生产业务日志集中采集与检索平台建设|无 Kubernetes、全新服务器:初始化 → 高可用 K8s → ELK 完整手册

执行规则:严格按编号顺序执行;每步只看本节的“执行机器 → 前置确认 → 命令/配置 → 输出样例 → 通过标准 → 失败处理”。任一前置条件或通过标准未满足时,停止在当前步。
唯一主线:新服务器盘点 → 三控制面 Kubernetes → Calico/Ingress/存储 → 集群内 ELK 与采集 → Kibana 验收 → 回滚与恢复。禁止在宿主机裸机安装 Elasticsearch、Logstash、Kibana 或 Filebeat。

0. 逐字检查通过规则

每一段、每一条命令、每一项配置、每一个参数、每一张图片说明,都必须逐字检查;只要发现一个错误、不一致、遗漏或无法执行的内容,就必须先改完。只有全部检查无问题,才可以通过。

每一步必须记录的真实执行结果

每个步骤执行后把终端或网页实际结果写进变更单;不允许只记录“已完成”。下面是统一记录格式;方括号内容是结果示例,不是命令或需替换的参数。

步骤:5.1 集群基础能力
执行机器:cp01
执行命令:kubectl get nodes
实际输出:[cp01 Ready control-plane;cp02 Ready control-plane;cp03 Ready control-plane;wk01/wk02/wk03 Ready]
判定:全部 Ready 才能部署 Ceph 与 Elasticsearch;存在 NotReady 时停止。

1. 固定参数表

本表是本手册唯一参数来源。全文命令、配置、输出样例均按这些固定值编写;后续替换环境时,只修改本表并同步复核引用位置。
参数固定值
API LB/VIP10.30.0.10:6443
API LB 节点lb01=10.30.0.8、lb02=10.30.0.9(均使用 ens160)
控制面cp01=10.30.0.11、cp02=10.30.0.12、cp03=10.30.0.13
Workerwk01=10.30.0.21、wk02=10.30.0.22、wk03=10.30.0.23
Pod/Service 网段10.244.0.0/16、10.96.0.0/12
Kubernetes/Elastic 版本v1.30.0、8.14.3
日志 Namespaceobservability
ES 存储fast-ssd、每节点 500Gi
Ceph 原始数据盘wk01/wk02/wk03 各一块未分区、未挂载的 /dev/sdb,容量均不小于 2TiB
IngressClass / 证书系统nginx / cert-manager
Kubernetes 集群域名cluster.local
Kibana 域名kibana.ops.internal
ES 日志索引 / 保留期logs-k8s-prod-* / 30 天
业务命名空间business-prod
证书签发器internal-ca(公司已审批的 cert-manager ClusterIssuer)
快照仓库prod-logs-cephfs(集群内 Rook-CephFS 共享目录)

2. 新服务器盘点

执行机器:所有控制面和 Worker。

# 查看系统、主机名、IP、资源、磁盘。
cat /etc/os-release # 输出发行版与版本,确认文档中的 Rocky/RHEL 系命令可用。
hostnamectl --static # 输出当前静态主机名,与资产表中的固定名称核对。
ip -br addr # 验证或配置本步骤所需的网络与端口连通性。
free -h # 输出内存与 Swap 使用量,确认新服务器资源基线。
df -hT # 输出挂载点、文件系统类型和容量,确认系统盘与数据盘状态。
# 确认没有旧容器、kubelet 或需保留业务。
docker ps -a 2>/dev/null || true # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
systemctl is-active containerd kubelet 2>/dev/null || true # 管理或验证本机系统服务状态。

预期结果:六台 Kubernetes 节点均能看到正确的系统版本、IP、CPU/内存和磁盘;Docker、containerd、kubelet 没有遗留业务容器或旧集群服务。 通过标准:主机名/IP 与参数表一致,机器无需保留的业务和旧集群状态。 失败处理:有业务、数据库、旧容器或 kubelet 时停止,不能 kubeadm reset。

3. 新服务器初始化

执行机器:cp01、cp02、cp03、wk01、wk02、wk03 的本机控制台。只在第 2 节确认“无保留业务”的新服务器执行;以下六个区块必须分别在标题指定的机器执行,不能从 cp01 通过未验证的 SSH 代替执行。

# cp01 本机:设置固定控制面主机名。
hostnamectl set-hostname cp01 # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# cp01 本机:重新生成克隆机唯一 machine-id。
rm -f /etc/machine-id # 清理当前发布机的临时文件或敏感会话变量。
# cp01 本机:创建新的 machine-id。
systemd-machine-id-setup # 生成本机唯一 machine-id,消除克隆机身份冲突。
# cp01 本机:启动时间同步服务。
systemctl enable --now chronyd # 管理或验证本机系统服务状态。
# cp01 本机:检查时间同步状态。
chronyc tracking # 输出时间同步源、偏移量和同步状态。
# cp01 本机:创建 Kubernetes 日志目录。
install -d -m 755 /var/log/kubernetes # 创建、复制或验证本步骤所需的本地受控文件。
# cp01 本机:创建 containerd 数据目录。
install -d -m 755 /data/containerd # 创建、复制或验证本步骤所需的本地受控文件。

执行机器:cp02 本机。

# cp02 本机:设置固定控制面主机名。
hostnamectl set-hostname cp02 # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# cp02 本机:重新生成克隆机唯一 machine-id。
rm -f /etc/machine-id # 清理当前发布机的临时文件或敏感会话变量。
# cp02 本机:创建新的 machine-id。
systemd-machine-id-setup # 生成本机唯一 machine-id,消除克隆机身份冲突。
# cp02 本机:启动时间同步服务。
systemctl enable --now chronyd # 管理或验证本机系统服务状态。
# cp02 本机:检查时间同步状态。
chronyc tracking # 输出时间同步源、偏移量和同步状态。
# cp02 本机:创建 Kubernetes 日志目录。
install -d -m 755 /var/log/kubernetes # 创建、复制或验证本步骤所需的本地受控文件。
# cp02 本机:创建 containerd 数据目录。
install -d -m 755 /data/containerd # 创建、复制或验证本步骤所需的本地受控文件。

执行机器:cp03 本机。

# cp03 本机:设置固定控制面主机名。
hostnamectl set-hostname cp03 # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# cp03 本机:重新生成克隆机唯一 machine-id。
rm -f /etc/machine-id # 清理当前发布机的临时文件或敏感会话变量。
# cp03 本机:创建新的 machine-id。
systemd-machine-id-setup # 生成本机唯一 machine-id,消除克隆机身份冲突。
# cp03 本机:启动时间同步服务。
systemctl enable --now chronyd # 管理或验证本机系统服务状态。
# cp03 本机:检查时间同步状态。
chronyc tracking # 输出时间同步源、偏移量和同步状态。
# cp03 本机:创建 Kubernetes 日志目录。
install -d -m 755 /var/log/kubernetes # 创建、复制或验证本步骤所需的本地受控文件。
# cp03 本机:创建 containerd 数据目录。
install -d -m 755 /data/containerd # 创建、复制或验证本步骤所需的本地受控文件。

执行机器:wk01 本机。

# wk01 本机:设置固定 Worker 主机名。
hostnamectl set-hostname wk01 # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# wk01 本机:重新生成克隆机唯一 machine-id。
rm -f /etc/machine-id # 清理当前发布机的临时文件或敏感会话变量。
# wk01 本机:创建新的 machine-id。
systemd-machine-id-setup # 生成本机唯一 machine-id,消除克隆机身份冲突。
# wk01 本机:启动时间同步服务。
systemctl enable --now chronyd # 管理或验证本机系统服务状态。
# wk01 本机:检查时间同步状态。
chronyc tracking # 输出时间同步源、偏移量和同步状态。
# wk01 本机:创建 Kubernetes 日志目录。
install -d -m 755 /var/log/kubernetes # 创建、复制或验证本步骤所需的本地受控文件。
# wk01 本机:创建 containerd 数据目录。
install -d -m 755 /data/containerd # 创建、复制或验证本步骤所需的本地受控文件。

执行机器:wk02 本机。

# wk02 本机:设置固定 Worker 主机名。
hostnamectl set-hostname wk02 # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# wk02 本机:重新生成克隆机唯一 machine-id。
rm -f /etc/machine-id # 清理当前发布机的临时文件或敏感会话变量。
# wk02 本机:创建新的 machine-id。
systemd-machine-id-setup # 生成本机唯一 machine-id,消除克隆机身份冲突。
# wk02 本机:启动时间同步服务。
systemctl enable --now chronyd # 管理或验证本机系统服务状态。
# wk02 本机:检查时间同步状态。
chronyc tracking # 输出时间同步源、偏移量和同步状态。
# wk02 本机:创建 Kubernetes 日志目录。
install -d -m 755 /var/log/kubernetes # 创建、复制或验证本步骤所需的本地受控文件。
# wk02 本机:创建 containerd 数据目录。
install -d -m 755 /data/containerd # 创建、复制或验证本步骤所需的本地受控文件。

执行机器:wk03 本机。

# wk03 本机:设置固定 Worker 主机名。
hostnamectl set-hostname wk03 # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# wk03 本机:重新生成克隆机唯一 machine-id。
rm -f /etc/machine-id # 清理当前发布机的临时文件或敏感会话变量。
# wk03 本机:创建新的 machine-id。
systemd-machine-id-setup # 生成本机唯一 machine-id,消除克隆机身份冲突。
# wk03 本机:启动时间同步服务。
systemctl enable --now chronyd # 管理或验证本机系统服务状态。
# wk03 本机:检查时间同步状态。
chronyc tracking # 输出时间同步源、偏移量和同步状态。
# wk03 本机:创建 Kubernetes 日志目录。
install -d -m 755 /var/log/kubernetes # 创建、复制或验证本步骤所需的本地受控文件。
# wk03 本机:创建 containerd 数据目录。
install -d -m 755 /data/containerd # 创建、复制或验证本步骤所需的本地受控文件。

执行机器:cp01、cp02、cp03、wk01、wk02、wk03,分别在本机执行。

# 当前机器:检查主机名、机器标识、时间和磁盘结果。
hostnamectl --static # 输出当前静态主机名,与资产表中的固定名称核对。
# 当前机器:输出唯一 machine-id。
cat /etc/machine-id # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# 当前机器:输出时间同步状态。
timedatectl status # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# 当前机器:输出磁盘类型与容量。
df -hT # 输出挂载点、文件系统类型和容量,确认系统盘与数据盘状态。

预期结果:主机名与资产表一致;machine-id 在每台机器不同;时间已同步;数据盘和日志目录可用。

通过标准:本机没有克隆机重复标识、时间漂移、未挂载数据盘或未知服务。

失败处理:machine-id 重复时停止加入集群;时间未同步时修 Chrony/DNS;磁盘未挂载时找存储负责人,不能先安装 Kubernetes。

3.1 系统、网络、SSH 与防火墙基线

执行机器:全部 Control Plane 与 Worker。系统更新、DNS、网关和防火墙必须先符合公司变更单,不能用“关防火墙”代替端口策略。

# 更新系统元数据和已批准的软件包。
dnf makecache # 刷新已批准 RPM 仓库元数据。
dnf upgrade -y # 安装仓库中已批准的系统安全更新。
# 查看默认网关和 DNS,确认可解析公司仓库与各节点主机名。
ip route # 输出默认网关和路由表,确认业务网段可达。
cat /etc/resolv.conf # 输出当前 DNS 服务器与搜索域。
getent hosts cp01 cp02 cp03 wk01 wk02 wk03 # 逐一验证六台 Kubernetes 节点名称解析。
# 启动防火墙,不关闭防火墙。
systemctl enable --now firewalld # 启动 firewalld 并设置开机自启。
# 在所有节点放行 kubelet、NodePort 和 Calico 所需端口;控制面端口在后续仅对控制面放行。
firewall-cmd --permanent --add-port=10250/tcp # 放行 kubelet HTTPS 端口。
firewall-cmd --permanent --add-port=30000-32767/tcp # 放行经批准的 NodePort 范围。
firewall-cmd --permanent --add-port=4789/udp # 放行 Calico VXLAN 封装流量。
firewall-cmd --reload # 重新加载永久规则使其立即生效。
# 验证当前放行端口。
firewall-cmd --list-ports # 输出已放行端口,必须包含 10250 和 NodePort 范围。
# 验证 SSH 服务正常,不修改现有认证方式前先保留已登录会话。
systemctl is-active sshd # 预期输出 active,确保保留远程救援通道。

预期结果:节点主机名可解析;firewalld 为 active;10250、NodePort、4789/udp 已放行;sshd 为 active。 通过标准:DNS、网关、时间、SSH、磁盘、端口均能在变更单和命令输出中验证。 失败处理:DNS 解析失败先修 DNS/hosts;端口策略与公司 ACL 冲突时由网络负责人放行,禁止关闭 firewalld。

3.2 部署 API LB/VIP

执行机器:lb01、lb02 与 cp01。LB/VIP 是生产 API Server 统一入口;不能把 cp01 的单机 IP 当作控制面长期访问地址。下面直接在两台专用 LB 节点部署 HAProxy + Keepalived;LB 节点不运行 Kubernetes、Elasticsearch 或业务 Pod。

# lb01/lb02:安装四层代理和 VRRP 高可用组件。
dnf install -y haproxy keepalived # 在两台专用 LB 安装 TCP 代理和 VIP 故障转移组件。
# lb01/lb02:允许 API TCP 6443 与 Keepalived VRRP 协议,不关闭防火墙。
firewall-cmd --permanent --add-port=6443/tcp # 放行 Kubernetes API 的 TCP 入口。
firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept' # 放行 Keepalived 使用的 VRRP 协议。
firewall-cmd --reload # 使 6443 与 VRRP 规则立即生效。
# lb01/lb02:验证两台专用 LB 的路由可达。
ping -c 2 10.30.0.8 # 从当前 LB 验证 lb01 的二次 ICMP 连通性。
ping -c 2 10.30.0.9 # 从当前 LB 验证 lb02 的二次 ICMP 连通性。

预期结果:两台 LB 可以访问,HAProxy 与 Keepalived 已安装,firewalld 仍然 active。 通过标准:lb01/lb02 的软件安装成功且只增加了 6443 与 VRRP 策略。 失败处理:LB 互通失败先修 VLAN、路由或 ACL;禁止先把 kubeadm endpoint 写为 cp01 单机 IP。

3.3 配置 HAProxy 和 Keepalived

执行机器:lb01 先执行主节点配置,lb02 执行备节点配置。两个节点的 HAProxy 后端完全一致;Keepalived 只有 state、priority 不同。

# 文件:/etc/haproxy/haproxy.cfg;lb01 与 lb02 内容相同。
global
    # 发送 HAProxy 日志到本机 syslog。
    log 127.0.0.1 local0
defaults
    # Kubernetes API 使用四层 TCP。
    mode tcp
    # 写入 TCP 连接日志。
    option tcplog
    # 后端建立连接超时。
    timeout connect 10s
    # 客户端空闲超时。
    timeout client 1m
    # 后端空闲超时。
    timeout server 1m
frontend kube-apiserver
    # 监听本机 6443 并接收 VIP 流量。
    bind *:6443
    # 转发到三控制面后端。
    default_backend kube-apiserver-backend
backend kube-apiserver-backend
    # 在健康的控制面之间轮询。
    balance roundrobin
    # cp01 API Server 与 TCP 健康检查。
    server cp01 10.30.0.11:6443 check
    # cp02 API Server 与 TCP 健康检查。
    server cp02 10.30.0.12:6443 check
    # cp03 API Server 与 TCP 健康检查。
    server cp03 10.30.0.13:6443 check
# 文件:/etc/keepalived/keepalived.conf;仅 lb01 使用。
vrrp_instance K8S_API_VIP {
    # lb01 初始持有 VIP。
    state MASTER
    # 两台 LB 的业务网卡。
    interface ens160
    # 两台 LB 必须相同的 VRRP ID。
    virtual_router_id 51
    # lb01 高于备节点的优先级。
    priority 120
    # VRRP 通告周期。
    advert_int 1
    authentication {
        # VRRP 一致性认证类型。
        auth_type PASS
        # 两台 LB 必须一致;生产网络同时使用受控 VLAN/ACL。
        auth_pass K8sVIP
    }
    virtual_ipaddress {
        # 集群统一 API VIP。
        10.30.0.10/24 dev ens160
    }
}
# 文件:/etc/keepalived/keepalived.conf;仅 lb02 使用。
vrrp_instance K8S_API_VIP {
    # lb02 只在 lb01 不可用时接管 VIP。
    state BACKUP
    # 与 lb01 相同的业务网卡。
    interface ens160
    # 必须与 lb01 相同。
    virtual_router_id 51
    # 必须低于 lb01。
    priority 100
    # 必须与 lb01 相同。
    advert_int 1
    authentication {
        # 必须与 lb01 相同。
        auth_type PASS
        # 必须与 lb01 相同。
        auth_pass K8sVIP
    }
    virtual_ipaddress {
        # 必须与 lb01 相同。
        10.30.0.10/24 dev ens160
    }
}
# lb01/lb02:写入对应配置后先校验 HAProxy 语法。
haproxy -c -f /etc/haproxy/haproxy.cfg # 校验配置语法,预期输出 Configuration file is valid。
# lb01/lb02:启动并设置服务开机自启。
systemctl enable --now haproxy keepalived # 启动代理与 VIP 服务并设为开机自启。
# lb01/lb02:确认两个服务 active。
systemctl is-active haproxy keepalived # 两行都必须输出 active。
# lb01:确认 VIP 位于主节点网卡。
ip -br addr show ens160 # lb01 上必须显示固定 VIP 10.30.0.10/24。
# 任意 Kubernetes 节点:初始化完成后验证 VIP API 端口。
nc -vz 10.30.0.10 6443 # 验证 VIP 的 TCP 6443 可建立连接。

预期结果:lb01 持有 10.30.0.10,两个 LB 的 haproxy/keepalived 均为 active;集群初始化后任意节点能连接 VIP:6443。 真实输出样例nc -vz 10.30.0.10 6443 输出 succeeded;停止 lb01 的 Keepalived 后,lb02 的 ip -br addr 显示 10.30.0.10/24通过标准:停止 lb01 的 keepalived 后,VIP 在 5 秒内漂到 lb02,nc -vz 10.30.0.10 6443 仍成功;HAProxy 后端固定为 cp01/cp02/cp03。 失败处理:VIP 不通查 journalctl -u keepalived -n 100 --no-pager、VRRP ACL、网卡和掩码;后端 unhealthy 查 API Server、6443 与防火墙;禁止在失败时把 endpoint 改成 cp01 IP。

4. 所有节点安装运行时与 kubeadm

执行机器:全部 Control Plane 与 Worker。前置条件是第 2、3 节已通过。

# 关闭当前和重启后的 Swap。
swapoff -a # 立即关闭当前 Swap,满足 kubelet 前置条件。
sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab # 注释 fstab 中的 Swap 挂载,避免重启后重新启用。
# 加载容器及桥接网络模块。
modprobe overlay # 加载容器镜像层所需 overlay 内核模块。
modprobe br_netfilter # 使桥接流量能进入 iptables/nft 规则链。
# 写入 Kubernetes 网络参数。
cat >/etc/sysctl.d/99-kubernetes.conf <<'EOF' # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# 桥接流量进入规则链。
net.bridge.bridge-nf-call-iptables = 1 # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
# 开启 IPv4 转发。
net.ipv4.ip_forward = 1 # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
EOF
# 立即生效。
sysctl --system # 重新加载所有 sysctl 配置并立即启用转发参数。
# 安装仓库管理插件;用于加入经公司批准的 Docker CE 运行时仓库。
dnf install -y dnf-plugins-core # 安装 dnf config-manager 所需插件。
# 添加 Docker CE 仓库,以获得固定来源的 containerd.io。
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 添加 containerd.io 的固定上游仓库。
# 配置 Kubernetes v1.30 官方 RPM 仓库;生产网络受限时替换为公司镜像,但路径、版本和 GPG 校验必须等价。
cat > /etc/yum.repos.d/kubernetes.repo <<'EOF' # 写入 Kubernetes v1.30 RPM 仓库定义,EOF 前内容会原样保存。
# Kubernetes v1.30 RPM 仓库标识。
[kubernetes] # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
# 仓库显示名称。
name=Kubernetes v1.30 # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
# Kubernetes v1.30 的 RPM 地址。
baseurl=https://pkgs.k8s.io/core:/stable:/v1.30/rpm/ # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
# 启用该仓库。
enabled=1 # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
# 校验 RPM 签名。
gpgcheck=1 # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
# Kubernetes RPM 签名公钥。
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.30/rpm/repodata/repomd.xml.key # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
# 让安装命令显式选择 Kubernetes 包来源。
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
EOF
# 安装运行时和固定大版本 Kubernetes 组件。
dnf install -y containerd.io kubelet-1.30.* kubeadm-1.30.* kubectl-1.30.* --disableexcludes=kubernetes # 安装 containerd 和 Kubernetes v1.30 组件。
# containerd 与 kubelet 使用 systemd cgroup。
containerd config default >/etc/containerd/config.toml # 生成完整 containerd 默认配置文件。
sed -ri 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml # 统一 containerd 与 kubelet 使用 systemd cgroup。
# 启动 containerd,启用 kubelet。
systemctl enable --now containerd # 启动 containerd 并设置开机自启。
systemctl enable kubelet # 设置 kubelet 开机自启;加入集群后由 kubeadm 拉起。
# 验证运行时、cgroup 和组件版本。
systemctl is-active containerd # 预期输出 active。
grep -n 'SystemdCgroup' /etc/containerd/config.toml # 必须显示 SystemdCgroup = true。
kubeadm version # 输出 kubeadm v1.30.x 版本。
kubelet --version # 输出 kubelet v1.30.x 版本。

预期结果:Swap 为 0,ip_forward 为 1,containerd active。 真实输出样例free -h 的 Swap 已用为 0Bsystemctl is-active containerd 输出 activekubeadm versionv1.30.通过标准systemctl is-active containerd 输出 activekubeadm versionkubelet --version 均为 v1.30.x。 失败处理:containerd 起不来查 journalctl -u containerd -n 100 --no-pager;版本不一致停止并统一版本。

5. 建立高可用三控制面集群

执行机器:先 cp01,随后 cp02/cp03,最后 Worker。cp01/cp02/cp03 额外放行控制面端口。

# 仅 cp01/cp02/cp03:放行 API、etcd、控制器和调度器端口。
firewall-cmd --permanent --add-port=6443/tcp # 放行 kube-apiserver。
firewall-cmd --permanent --add-port=2379-2380/tcp # 放行三控制面之间的 etcd 客户端和对等通信。
firewall-cmd --permanent --add-port=10257/tcp # 放行 kube-controller-manager 健康检查。
firewall-cmd --permanent --add-port=10259/tcp # 放行 kube-scheduler 健康检查。
firewall-cmd --reload # 应用控制面永久规则。
# 仅 cp01:初始化第一个控制面,API 走 LB/VIP。
kubeadm init --kubernetes-version v1.30.0 --control-plane-endpoint 10.30.0.10:6443 --upload-certs --pod-network-cidr 10.244.0.0/16 --service-cidr 10.96.0.0/12 --cri-socket unix:///run/containerd/containerd.sock # 在 cp01 通过 VIP 初始化并上传控制面证书。
# 仅 cp01:配置 kubectl。
mkdir -p /root/.kube # 创建 root 用户 kubectl 配置目录。
cp /etc/kubernetes/admin.conf /root/.kube/config # 复制 cp01 管理 kubeconfig,仅保留在控制面。
# 仅 cp01:kubeadm init 最后一段会一次性打印控制面 join 命令;把该完整命令写入受控变更单。
# cp02/cp03:仅执行 init 输出的那条完整 control-plane join 命令;它已包含 --control-plane 与一次性 certificate key。
# Worker:创建 2 小时有效的普通 join 命令,复制原样到受控变更单后仅在 Worker 执行。
kubeadm token create --ttl 2h --print-join-command # 生成仅两小时有效的 Worker join 完整命令。
# Worker:只执行上一条命令的完整输出,不能添加 --control-plane。
kubectl get nodes # 验证三控制面和 Worker 已注册;网络安装前 NotReady 是预期状态。
# Kubernetes v1.30 使用 readyz 健康端点,不使用已废弃的 componentstatuses。
kubectl get --raw='/readyz?verbose' # 读取 API Server readyz 逐项健康状态。

预期结果:所有节点出现;安装网络前 NotReady 正常。 通过标准:三台控制面和全部 Worker 都出现在节点列表;控制面 join 输出为成功。 失败处理:join 失败查 6443、token、证书 hash、时间同步和 kubelet 日志;token 过期后在 cp01 执行 kubeadm token create --print-join-command

6. Calico、CoreDNS、Ingress 与存储验收

执行机器:cp01。集群基础能力没有全部通过,禁止创建 Elasticsearch PVC 或部署日志业务。

# 仅 cp01:部署公司审计的 Calico 清单。
grep -n '10.244.0.0/16' /srv/k8s-manifests/calico-v3.28.1.yaml # 执行本机 Kubernetes 运行时或系统基线配置并用于验证。
kubectl apply -f /srv/k8s-manifests/calico-v3.28.1.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
# 验证网络和节点。
kubectl get pods -n kube-system # 查询当前 Kubernetes 资源状态,用于验证本步骤结果。
kubectl get nodes # 查询当前 Kubernetes 资源状态,用于验证本步骤结果。
# 确认生产 StorageClass。
kubectl get storageclass # 查询当前 Kubernetes 资源状态,用于验证本步骤结果。
# 确认 IngressClass。
kubectl get ingressclass # 查询当前 Kubernetes 资源状态,用于验证本步骤结果。
# 创建 cert-manager 命名空间。
kubectl create namespace cert-manager --dry-run=client -o yaml | kubectl apply -f - # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
# 使用公司审计并缓存的固定 chart 安装证书控制器和 CRD。
helm upgrade --install cert-manager /srv/charts/cert-manager-v1.15.1.tgz --namespace cert-manager --set crds.enabled=true --wait --timeout 10m # 按固定版本安装或升级已批准的 Helm Chart。
# 创建本日志平台专用的 CA 根证书和内部签发器;私钥只保存在 cert-manager Secret。
cat > /srv/k8s-manifests/internal-ca.yaml <<'EOF' # 写入内部 CA 引导、根证书和签发器清单。
apiVersion: cert-manager.io/v1 # 声明 Kubernetes 或扩展资源 API 版本。
kind: ClusterIssuer # 声明资源对象类型。
metadata: # 资源元数据开始。
  name: selfsigned-bootstrap # 固定资源名称。
spec: # 资源期望状态和配置规格开始。
  selfSigned: {} # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
---
apiVersion: cert-manager.io/v1 # 声明 Kubernetes 或扩展资源 API 版本。
kind: Certificate # 声明资源对象类型。
metadata: # 资源元数据开始。
  name: observability-root-ca # 固定资源名称。
  namespace: cert-manager # 资源所在命名空间。
spec: # 资源期望状态和配置规格开始。
  isCA: true # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
  commonName: observability-root-ca # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
  secretName: observability-root-ca-keypair # 引用或写入指定 Secret。
  issuerRef: # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
    name: selfsigned-bootstrap # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
    kind: ClusterIssuer # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
---
apiVersion: cert-manager.io/v1 # 声明 Kubernetes 或扩展资源 API 版本。
kind: ClusterIssuer # 声明资源对象类型。
metadata: # 资源元数据开始。
  name: internal-ca # 固定资源名称。
spec: # 资源期望状态和配置规格开始。
  ca: # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
    secretName: observability-root-ca-keypair # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
EOF
kubectl apply --dry-run=server -f /srv/k8s-manifests/internal-ca.yaml # 校验根 CA 与 ClusterIssuer 配置,不创建资源。
kubectl apply -f /srv/k8s-manifests/internal-ca.yaml # 创建自签引导签发器、根 CA 证书和内部签发器。
kubectl -n cert-manager wait --for=condition=Ready certificate/observability-root-ca --timeout=180s # 等待根 CA 证书 Secret 生成。
kubectl get clusterissuer internal-ca # 验证后续 ELK TLS 使用的内部签发器已存在。
# 在集群内检查 CoreDNS 是否能解析 Kubernetes Service。
kubectl run dns-check --image=busybox:1.36 --restart=Never --rm -i -- nslookup kubernetes.default.svc.cluster.local # 临时 Pod 查询核心 Service 域名;结束后自动删除。
# 创建 Ingress 命名空间。
kubectl create namespace ingress-nginx --dry-run=client -o yaml | kubectl apply -f - # 幂等创建 Ingress Controller 命名空间。
# 使用公司审计并缓存的固定 chart 安装双副本 Ingress Controller。
helm upgrade --install ingress-nginx /srv/charts/ingress-nginx-4.11.1.tgz --namespace ingress-nginx --set controller.replicaCount=2 --set controller.service.type=LoadBalancer --set controller.ingressClassResource.name=nginx --wait --timeout 10m # 安装固定 Chart、双副本控制器与 nginx IngressClass。
# 检查控制器、Service、IngressClass。
kubectl -n ingress-nginx get pod,svc # 确认双副本控制器和 LoadBalancer Service 已就绪。
kubectl get ingressclass # 必须显示 nginx IngressClass。

预期结果:Calico 清单包含 10.244.0.0/16;每台节点有一个 calico-node;coredns、kube-proxy 为 Running;cert-manager、双副本 Ingress Controller Running,存在 nginx IngressClass、internal-ca 与 fast-ssd StorageClass。 通过标准:kubectl get nodes 的全部节点为 Ready;kubectl get storageclass 能看到 fast-ssd;Ingress 控制器 Pod 和 cert-manager Pod 为 Running。 失败处理:Calico 异常查 Pod CIDR、节点网络和宿主机防火墙;DNS 失败查 CoreDNS Pod 和日志;PVC 能力缺失先补 StorageClass;internal-ca 缺失时由 PKI 管理员按变更单导入企业 CA;Ingress 缺失先安装经审批的 ingress-nginx,不能把 Kibana 用 NodePort 暴露为长期入口。

6.1 部署三副本 Ceph CSI 存储并创建 fast-ssd

执行机器:cp01 发布机;wk01、wk02、wk03 存储节点。此步骤使用参数表指定的三块空白 /dev/sdb 创建生产 Ceph 存储。执行前确认三块磁盘没有分区、文件系统、PV、业务数据或历史 Ceph 签名;不满足时停止,不能格式化未知磁盘。

# wk01/wk02/wk03:只读确认指定磁盘的型号、容量、文件系统和挂载状态。
lsblk -f /dev/sdb # 只读输出 /dev/sdb 的分区、文件系统、标签和挂载点。
# wk01/wk02/wk03:未发现任何挂载、FSTYPE、分区或数据后才可通过本步。
wipefs -n /dev/sdb # 只读探测现有签名;出现任何签名即停止,不执行擦除。
# cp01:创建 Rook 命名空间。
kubectl create namespace rook-ceph --dry-run=client -o yaml | kubectl apply -f - # 幂等创建 Rook Ceph 隔离命名空间。
# cp01:使用公司审计并缓存的固定 chart 安装 Rook Ceph Operator。
helm upgrade --install rook-ceph /srv/charts/rook-ceph-v1.14.9.tgz --namespace rook-ceph --wait --timeout 10m # 安装已审计的 Rook Operator 并等待其就绪。
# cp01:确认 Operator 就绪。
kubectl -n rook-ceph get pod # 确认 rook-ceph-operator 为 1/1 Running。

预期结果:每台 Worker 的 /dev/sdb 均为空白;rook-ceph Operator 为 Running。 通过标准:三块磁盘都没有业务数据或挂载,且 Operator Pod Ready 1/1。 失败处理lsblkwipefs -n 显示数据时立即停止并交由存储负责人确认;Operator 拉取失败查公司镜像仓库和节点网络,禁止改用主机本地目录作为 ES 生产卷。

# 文件:20-rook-ceph-cluster.yaml
# Rook Ceph 自定义资源 API。
apiVersion: ceph.rook.io/v1
# 创建三节点 Ceph 集群。
kind: CephCluster
metadata:
  # Ceph 集群名称。
  name: rook-ceph
  # Rook 固定命名空间。
  namespace: rook-ceph
spec:
  # 固定 Ceph 镜像版本。
  cephVersion:
    image: quay.io/ceph/ceph:v18.2.4
  # Ceph 本机状态目录。
  dataDirHostPath: /var/lib/rook
  mon:
    # 三个 Monitor 保证仲裁。
    count: 3
    # 禁止两个 Monitor 落在同一节点。
    allowMultiplePerNode: false
  mgr:
    # 两个 Manager 保证管理面高可用。
    count: 2
  dashboard:
    # 不开启未受保护的 Ceph Dashboard。
    enabled: false
  storage:
    # 不自动接管未知节点。
    useAllNodes: false
    # 不自动接管未知磁盘。
    useAllDevices: false
    nodes:
      - name: wk01
        devices:
          - name: sdb
      - name: wk02
        devices:
          - name: sdb
      - name: wk03
        devices:
          - name: sdb
---
# Rook Ceph 自定义资源 API。
apiVersion: ceph.rook.io/v1
# 创建三副本 RBD 池。
kind: CephBlockPool
metadata:
  # RBD 池名称。
  name: replicapool
  # Rook 固定命名空间。
  namespace: rook-ceph
spec:
  replicated:
    # 每个块保存三份副本。
    size: 3
---
# Kubernetes StorageClass API。
apiVersion: storage.k8s.io/v1
# 创建 ES 使用的动态卷类型。
kind: StorageClass
metadata:
  # 与 ES YAML 一致的存储类名称。
  name: fast-ssd
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
  # Rook Ceph 所在命名空间。
  clusterID: rook-ceph
  # 使用三副本 RBD 池。
  pool: replicapool
  # RBD 镜像格式。
  imageFormat: "2"
  # RBD 镜像所需功能。
  imageFeatures: layering
  # CSI 创建卷所用 Secret 名称。
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
  # CSI 创建卷 Secret 命名空间。
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  # CSI 挂载卷所用 Secret 名称。
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
  # CSI 挂载卷 Secret 命名空间。
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

配置详解(20-rook-ceph-cluster.yamluseAllNodes/useAllDevices: false 防止 Rook 接管未知资源;仅 wk01、wk02、wk03 的 sdb 被允许作为 OSD。三 Monitor 提供仲裁、两 Manager 提供管理面冗余,replicapool.size: 3 使每块 RBD 数据保留三副本。fast-ssd 经 RBD CSI 动态供应卷;Retain 防止删除 PVC 时误删数据,WaitForFirstConsumer 在调度节点确定后绑定卷,适合 ES 独立 RWO 数据盘。

配置作用:提供三副本、可动态供应的 ES 块存储。 生效结果:每台 ES 获得一块独立 500Gi RBD PVC;删除工作负载不会自动抹除数据。 注意事项:三副本原始容量按 6TiB 下限评估;发现任何 sdb 签名或业务数据立即停止。

# cp01:先让 API Server 校验对象字段,再创建 Ceph 集群、RBD 池和 StorageClass。
kubectl apply --dry-run=server -f 20-rook-ceph-cluster.yaml # 由 API Server 校验 Ceph CR、池和 StorageClass 字段。
kubectl apply -f 20-rook-ceph-cluster.yaml # 创建三节点 Ceph、三副本 RBD 池与 fast-ssd。
# cp01:等待 Ceph 集群进入 Ready;初始数据同步时间由磁盘性能决定。
kubectl -n rook-ceph wait --for=condition=Ready cephcluster/rook-ceph --timeout=30m # 最多等待 30 分钟,避免 Ceph 未就绪就创建 ES PVC。
# cp01:确认三份 OSD、RBD 池与 fast-ssd 均可用。
kubectl -n rook-ceph get pod # 检查 operator、mon、mgr、osd 和 CSI Pod 都已 Running。
kubectl -n rook-ceph get cephcluster,cephblockpool # 确认 CephCluster Ready、replicapool 已创建。
kubectl get storageclass fast-ssd # 核对 provisioner、Retain 回收策略和延迟绑定策略。

预期结果:CephCluster 为 Ready,三个 Worker 均有 OSD,fast-ssd 存在且 reclaimPolicy 为 Retain。 真实输出样例kubectl -n rook-ceph get cephcluster 显示 rook-ceph Readykubectl get storageclass fast-ssd 的 PROVISIONER 为 rook-ceph.rbd.csi.ceph.com通过标准:三份副本存储可用、StorageClass 可动态创建 RWO PVC;ES 的 3 × 500Gi 逻辑卷在 size: 3 副本下消耗约 4.5TiB 原始容量,按至少 25% 容量余量计算,三块原始盘合计必须不少于 6TiB。 失败处理:Ceph Pending/Health 异常查 kubectl -n rook-ceph get pod 与 Operator 日志;磁盘不足、单节点故障或三副本容量不够时停止 ELK 部署,不能降低副本数后冒充生产高可用。

7. 在新集群部署 ELK 项目

执行机器:cp01 发布机。以下对象全部运行在 Kubernetes 内:ECK Operator、三节点 Elasticsearch、Kibana、双副本 Logstash、Filebeat DaemonSet;禁止在宿主机使用 dnf 安装 ES/Logstash/Kibana。

# 添加固定版本 ECK chart 仓库。
helm repo add elastic https://helm.elastic.co # 注册 Elastic 官方 Helm 仓库。
# 更新 chart 元数据。
helm repo update # 获取仓库索引,以解析固定 2.13.0 Chart。
# 创建 Operator 命名空间。
kubectl create namespace elastic-system --dry-run=client -o yaml | kubectl apply -f - # 幂等创建仅运行 ECK Operator 的命名空间。
# 安装固定版本 ECK Operator。
helm upgrade --install eck-operator elastic/eck-operator --version 2.13.0 --namespace elastic-system --wait --timeout 5m # 安装固定 ECK 版本并等待就绪。
# 验证 Operator 已运行。
kubectl -n elastic-system get pod # 确认 eck-operator 为 1/1 Running。

预期结果:eck-operator 为 Running。 通过标准:Helm 命令成功,Operator Pod Ready 1/1。 失败处理:ImagePullBackOff 查镜像源;Pending 查节点资源;Operator 未 Ready 时不创建 Elasticsearch。

7.1 创建三节点 Elasticsearch 与 Kibana

执行机器:cp01 发布机。PVC 使用第 6 节已经验收的 fast-ssd;在提交本节 YAML 前,必须先完成第 11.0 节的 CephFS 快照 PVC 并确认 es-snapshot-rwx 为 Bound;镜像、证书、账号全部由 ECK 管理。

# 文件:30-elasticsearch-kibana.yaml
# ECK Elasticsearch API。
apiVersion: elasticsearch.k8s.elastic.co/v1
# 创建 ES 集群。
kind: Elasticsearch
metadata:
  # ES 集群名称。
  name: prod-logs
  # 日志命名空间。
  namespace: observability
spec:
  # 固定 Elastic 版本。
  version: 8.14.3
  nodeSets:
    - name: master-data
      # 生产三节点。
      count: 3
      config:
        # 节点角色。
        node.roles: [ master, data_hot, ingest ]
        # 三个 ES Pod 共用的 CephFS 快照目录;由第 11.0 节 PVC 挂载。
        path.repo: [ "/mnt/es-snapshots" ]
      podTemplate:
        spec:
          affinity:
            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                - labelSelector:
                    matchExpressions:
                      - key: elasticsearch.k8s.elastic.co/cluster-name
                        operator: In
                        values: [ prod-logs ]
                  topologyKey: kubernetes.io/hostname
          containers:
            - name: elasticsearch
              volumeMounts:
                # 将共享 CephFS PVC 挂载为 ES 快照仓库目录。
                - name: es-snapshot-rwx
                  mountPath: /mnt/es-snapshots
              resources:
                requests:
                  cpu: "2"
                  memory: 4Gi
                limits:
                  cpu: "4"
                  memory: 8Gi
          volumes:
            # 共享快照 PVC 名称;必须在提交 ES 资源前先 Bound。
            - name: es-snapshot-rwx
              persistentVolumeClaim:
                claimName: es-snapshot-rwx
      volumeClaimTemplates:
        - metadata:
            # 数据 PVC 名称。
            name: elasticsearch-data
          spec:
            # 单节点独占数据卷。
            accessModes: [ ReadWriteOnce ]
            resources:
              requests:
                # 每节点数据容量。
                storage: 500Gi
            # 已验收的生产存储类。
            storageClassName: fast-ssd
---
# ECK Kibana API。
apiVersion: kibana.k8s.elastic.co/v1
# 创建 Kibana。
kind: Kibana
metadata:
  # Kibana 名称。
  name: prod-kibana
  # 日志命名空间。
  namespace: observability
spec:
  # 与 ES 相同版本。
  version: 8.14.3
  count: 1
  elasticsearchRef:
    # 关联 ES 集群。
    name: prod-logs
  podTemplate:
    spec:
      containers:
        - name: kibana
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: "1"
              memory: 2Gi

配置详解(30-elasticsearch-kibana.yaml:Elasticsearch 的 version: 8.14.3 与 Kibana 版本必须一致;count: 3 形成三节点选主和数据副本基础,node.roles 同时启用 master、hot data、ingest。path.repo 只指向受控 CephFS 快照目录。反亲和规则以集群标签和主机名为维度,禁止两个 ES Pod 同机;每个 volumeClaimTemplate 通过 fast-ssd 创建独立 RWO 500Gi PVC,不能互相共享。Kibana 通过 elasticsearchRef: prod-logs 由 ECK 受控连接 ES,账号和 CA 由 ECK 管理,文档不创建明文连接信息。

配置作用:创建三节点 Elasticsearch 数据面与由 ECK 管理的 Kibana 查询层。 生效结果:三块独立 PVC Bound、ES health 为 green、Kibana 仅通过内部 Service 被 Ingress 反代。 注意事项:不允许删除 PVC 处理启动失败;版本、容量、节点数和快照路径变化必须单独评审。

# 创建日志项目命名空间。
kubectl create namespace observability --dry-run=client -o yaml | kubectl apply -f - # 幂等创建日志平台隔离命名空间。
# 客户端校验 YAML。
kubectl apply --dry-run=server -f 30-elasticsearch-kibana.yaml # 使用 API Server 校验 ECK CRD、PVC 与准入策略。
# 创建 ES 和 Kibana。
kubectl apply -f 30-elasticsearch-kibana.yaml # 创建三节点 ES 与 Kibana。
# 观察资源、Pod 和 PVC 状态。
kubectl -n observability get elasticsearch,kibana,pod,pvc -w # 持续观察 ECK 资源、Pod 与 PVC 直到就绪。

预期结果:三个 ES Pod Running,三个 PVC Bound,Elasticsearch health 为 green,Kibana Pod Running。 真实输出样例kubectl -n observability get elasticsearch 的 HEALTH 为 green;三个数据 PVC 均为 Bound通过标准:ES 没有 Red/Yellow,PVC 无 Pending。 失败处理:PVC Pending 查 fast-ssd;ES 不健康查 ECK Events、节点资源、PVC;不能删除 PVC 解决。

7.2 Logstash 与 Filebeat 日志链路

执行机器:cp01 发布机。Logstash 以两个副本接收 Filebeat;Filebeat 用 DaemonSet 每节点采集容器日志。

前置条件:日志平台管理员已在 ES 中创建最小权限账号 logstash_writer(只可写 logs-k8s-prod-*)。本步骤由 internal-ca 签发 Logstash 服务证书;该签发器必须把 CA 链写入 ca.crt。如果公司 CA 不提供该字段,先由 PKI 管理员提供 CA 文件并建立同名 Secret,不能关闭 TLS 或把 verification_mode 改成 none

# 读取 Logstash 最小权限账号密码;输入不回显且不写入命令历史。
read -s -p 'logstash_writer password: ' ES_WRITER_PASSWORD; echo # 静默读取当前会话密码,不回显且不写入命令历史。
# 用当前会话密码创建或更新 Secret;密码不会写入 YAML、Git 或 ConfigMap。
kubectl -n observability create secret generic logstash-es-credentials --from-literal=username=logstash_writer --from-literal=password="$ES_WRITER_PASSWORD" --dry-run=client -o yaml | kubectl apply -f - # 读取组件最近日志,定位采集、TLS 或输出异常。
# 立即清除当前 shell 的密码变量。
unset ES_WRITER_PASSWORD # 清理当前发布机的临时文件或敏感会话变量。
# 在当前目录创建 Logstash TLS 证书对象文件。
cat > 30-logstash-certificate.yaml <<'EOF' # 写入 Logstash Beats Service 的 TLS 证书申请清单。
# cert-manager API。
apiVersion: cert-manager.io/v1 # 声明 Kubernetes 或扩展资源 API 版本。
# 为 Logstash Service 申请 TLS 证书。
kind: Certificate # 声明资源对象类型。
metadata: # 资源元数据开始。
  # Certificate 名称。
  name: logstash-beats # 固定资源名称。
  # 日志命名空间。
  namespace: observability # 资源所在命名空间。
spec: # 资源期望状态和配置规格开始。
  # 签发后的 tls.crt、tls.key、ca.crt 都写到这个 Secret。
  secretName: logstash-beats-tls # 引用或写入指定 Secret。
  # Filebeat 连接时使用的服务域名。
  dnsNames: # 证书允许的服务域名列表。
    - logstash-beats.observability.svc # 列表中的一个固定配置项。
    - logstash-beats.observability.svc.cluster.local # 列表中的一个固定配置项。
  issuerRef: # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
    # 统一参数表中确认过的企业 CA。
    name: internal-ca # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
    kind: ClusterIssuer # 该字段使用本手册已固定的生产参数,修改前必须走变更评审。
EOF
# 先由 API Server 校验对象,再提交签发申请。
kubectl apply --dry-run=server -f 30-logstash-certificate.yaml # 校验证书对象和内部 CA 引用。
kubectl apply -f 30-logstash-certificate.yaml # 提交 Logstash Service TLS 证书申请。
# 等待签发完成;不打印私钥或密码。
kubectl -n observability wait --for=condition=Ready certificate/logstash-beats --timeout=180s # 等待目标资源达到就绪状态,超时则停止后续操作。
# 从 TLS Secret 的 CA 链建立仅供 Filebeat 挂载的 CA Secret。
kubectl -n observability get secret logstash-beats-tls -o jsonpath='{.data.ca\.crt}' | base64 -d > ./logstash-beats-ca.crt # 读取组件最近日志,定位采集、TLS 或输出异常。
kubectl -n observability create secret generic logstash-beats-ca --from-file=ca.crt=./logstash-beats-ca.crt --dry-run=client -o yaml | kubectl apply -f - # 读取组件最近日志,定位采集、TLS 或输出异常。
# 确认证书与凭据 Secret 存在,不显示敏感值。
kubectl -n observability get secret logstash-es-credentials logstash-beats-tls logstash-beats-ca prod-logs-es-http-ca-internal # 读取组件最近日志,定位采集、TLS 或输出异常。
# 删除发布机的临时 CA 文件。
rm -f ./logstash-beats-ca.crt # 清理当前发布机的临时文件或敏感会话变量。

预期结果:三个 Secret 均存在。 通过标准:没有明文密码进入 YAML、Git、ConfigMap 或终端日志。 失败处理:没有 TLS Secret 时不启动 Filebeat;账号没有最小权限时由 ES 管理员修复,不能改用 elastic 超级账号。

# 文件:31-logstash-filebeat.yaml
# Logstash 不需要调用 Kubernetes API,使用独立的空权限服务账号。
apiVersion: v1
kind: ServiceAccount
metadata:
  name: logstash
  namespace: observability
---
# Logstash Pipeline。
apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-pipeline
  namespace: observability
data:
  logstash.conf: |-
    input {
      beats {
        port => 5044
        ssl_enabled => true
        ssl_certificate => "/usr/share/logstash/config/beats/tls.crt"
        ssl_key => "/usr/share/logstash/config/beats/tls.key"
      }
    }
    filter {
      mutate { add_field => { "log_pipeline" => "prod-elk" } }
    }
    output {
      elasticsearch {
        hosts => [ "https://prod-logs-es-http:9200" ]
        user => "${ES_USERNAME}"
        password => "${ES_PASSWORD}"
        ssl_certificate_authorities => [ "/usr/share/logstash/config/es-ca/ca.crt" ]
        index => "logs-k8s-prod-%{+YYYY.MM.dd}"
      }
    }
---
# Logstash Service。
apiVersion: v1
kind: Service
metadata:
  name: logstash-beats
  namespace: observability
spec:
  selector:
    app: logstash
  ports:
    - name: beats
      port: 5044
      targetPort: 5044
---
# Logstash 双副本。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: logstash
  namespace: observability
spec:
  replicas: 2
  selector:
    matchLabels:
      app: logstash
  template:
    metadata:
      labels:
        app: logstash
    spec:
      serviceAccountName: logstash
      containers:
        - name: logstash
          image: docker.elastic.co/logstash/logstash:8.14.3
          ports:
            - name: beats
              containerPort: 5044
          env:
            - name: ES_USERNAME
              valueFrom:
                secretKeyRef:
                  name: logstash-es-credentials
                  key: username
            - name: ES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: logstash-es-credentials
                  key: password
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: "1"
              memory: 2Gi
          readinessProbe:
            tcpSocket:
              port: beats
            initialDelaySeconds: 20
            periodSeconds: 10
          volumeMounts:
            - name: pipeline
              mountPath: /usr/share/logstash/pipeline/logstash.conf
              subPath: logstash.conf
              readOnly: true
            - name: beats-tls
              mountPath: /usr/share/logstash/config/beats
              readOnly: true
            - name: es-ca
              mountPath: /usr/share/logstash/config/es-ca
              readOnly: true
      volumes:
        - name: pipeline
          configMap:
            name: logstash-pipeline
        - name: beats-tls
          secret:
            secretName: logstash-beats-tls
        - name: es-ca
          secret:
            secretName: prod-logs-es-http-ca-internal

配置详解(31-logstash-filebeat.yamlServiceAccount/logstash 不绑定任何 Role,Logstash 无需访问 Kubernetes API。ConfigMap/logstash-pipeline 的 Beats input 固定监听 5044,ssl_enabled: true 强制 Filebeat 使用 TLS,证书与私钥来自 logstash-beats-tls;filter 只添加固定 log_pipeline=prod-elk 标记,不删除原始字段。Elasticsearch output 只访问 ECK 生成的内部 HTTPS Service prod-logs-es-http:9200,用户名/密码由 logstash-es-credentials 注入,CA 来自 ECK 的 HTTP CA Secret,索引固定为 logs-k8s-prod-YYYY.MM.ddService/logstash-beatsapp: logstash 选择后端,并把 Service 5044 转到容器 5044。Deployment 的 replicas: 2 是接收层高可用,selector 与 Pod label 必须一致;readinessProbe 只有 TCP 5044 可用时才把 Pod 加入 Endpoint。三个 volume 分别把 Pipeline、Beats TLS 和 ES CA 以只读方式挂载,Secret 从不写入 ConfigMap。

配置作用:提供双副本、TLS 受保护的 Filebeat 接入层,并把日志以最小权限写入集群内 Elasticsearch。 生效结果:Service 的 Ready Endpoint 必须恰好对应两个 Ready Logstash Pod;ES 9200 与 Logstash 5044 都不创建公网 Service。 注意事项prod-logs-es-http-ca-internal 依赖 ECK 集群名 prod-logs;改名时必须同步改 Secret 引用。禁止使用 elastic 超级账号、http://ssl_verification_mode => none 或通过 NodePort 暴露 5044。

# 先由 API Server 校验 Logstash ConfigMap、Secret 引用、Service 和 Deployment。
kubectl apply --dry-run=server -f 31-logstash-filebeat.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
# 创建 Logstash Service 和 Deployment。
kubectl apply -f 31-logstash-filebeat.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
# 等待两个副本可用。
kubectl -n observability rollout status deployment/logstash --timeout=180s # 等待目标资源达到就绪状态,超时则停止后续操作。
# 验证 Service 有后端 Endpoint。
kubectl -n observability get pod,svc,endpoints -l app=logstash # 读取组件最近日志,定位采集、TLS 或输出异常。

预期结果:两个 Logstash Pod Running,Service Endpoint 为两个。 真实输出样例kubectl -n observability get endpoints logstash-beats 显示两个 10.244.x.x:5044 地址。 通过标准:Service 的 5044 有 Ready Endpoint。 失败处理:无 Endpoint 查 selector/Pod Ready;Pod 崩溃看 kubectl logs deploy/logstash。

7.2.1 Filebeat 最小权限 RBAC

# 文件:31-filebeat-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: filebeat
  namespace: observability
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: filebeat-metadata-reader
rules:
  - apiGroups: [""]
    resources: ["pods", "namespaces", "nodes"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: filebeat-metadata-reader
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: filebeat-metadata-reader
subjects:
  - kind: ServiceAccount
    name: filebeat
    namespace: observability
# 先校验最小权限 RBAC 对象。
kubectl apply --dry-run=server -f 31-filebeat-rbac.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
# 应用最小权限 RBAC。
kubectl apply -f 31-filebeat-rbac.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
# 验证服务账号能读取 Pod 元数据。
kubectl auth can-i list pods --as=system:serviceaccount:observability:filebeat -A # 验证服务账号实际 RBAC 权限,防止权限过大或不足。
# 验证服务账号没有 cluster-admin 等全权限。
kubectl auth can-i '*' '*' --as=system:serviceaccount:observability:filebeat -A # 验证服务账号实际 RBAC 权限,防止权限过大或不足。

预期结果:第一条输出 yes,第二条输出 no。 通过标准:Filebeat 没有 cluster-admin。 失败处理:输出 no 时检查 ClusterRoleBinding;禁止以 cluster-admin 绕过。

7.3 Filebeat DaemonSet 与日志字段

执行机器:cp01 发布机。Filebeat 每个节点一个,只读宿主机容器日志;先灰度到一个 Worker 验证后,再扩大到全部节点。

# 文件:32-filebeat.yaml
# Filebeat 配置。
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: observability
data:
  filebeat.yml: |-
    filebeat.inputs:
      - type: filestream
        id: container-logs
        enabled: true
        paths:
          - /var/log/containers/*.log
        parsers:
          - container: ~
        processors:
          - add_kubernetes_metadata:
              host: ${NODE_NAME}
    output.logstash:
      hosts: ["logstash-beats.observability.svc:5044"]
      ssl:
        certificate_authorities: ["/usr/share/filebeat/certs/ca.crt"]
        verification_mode: full
---
# 每节点一个 Filebeat。
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: observability
spec:
  selector:
    matchLabels:
      app: filebeat
  template:
    metadata:
      labels:
        app: filebeat
    spec:
      serviceAccountName: filebeat
      # 仅在已批准并显式打标签的 Worker 运行,保证先 wk01 灰度。
      nodeSelector:
        logs.ops.internal/filebeat: enabled
      containers:
        - name: filebeat
          image: docker.elastic.co/beats/filebeat:8.14.3
          args: ["-c", "/usr/share/filebeat/filebeat.yml", "-e"]
          securityContext:
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
          env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
          volumeMounts:
            - name: config
              mountPath: /usr/share/filebeat/filebeat.yml
              subPath: filebeat.yml
            - name: containers
              mountPath: /var/log/containers
              readOnly: true
            - name: logstash-ca
              mountPath: /usr/share/filebeat/certs
              readOnly: true
            - name: filebeat-data
              mountPath: /usr/share/filebeat/data
      volumes:
        - name: config
          configMap:
            name: filebeat-config
        - name: containers
          hostPath:
            path: /var/log/containers
            type: Directory
        - name: logstash-ca
          secret:
            secretName: logstash-beats-ca
        - name: filebeat-data
          emptyDir: {}

配置详解(32-filebeat.yamlConfigMap/filebeat-configfilebeat.yml 挂载为唯一采集配置;filestream 输入的 id: container-logs 固定状态标识,paths 只匹配 Kubernetes CRI 标准路径 /var/log/containers/*.logcontainer parser 解析 CRI 日志时间戳、stdout/stderr 与内容,add_kubernetes_metadata.host: ${NODE_NAME} 只关联当前节点的 Pod 元数据。output.logstash.hosts 固定到集群内 TLS Service 5044,certificate_authorities 指向 CA Secret,verification_mode: full 同时校验 CA、证书链和 Service DNS,禁止降级。DaemonSet 的 selector/template app: filebeat 必须一致;serviceAccountName: filebeat 仅绑定前一节的元数据读取权限;nodeSelector 是灰度开关,只有显式打上 logs.ops.internal/filebeat=enabled 的节点能调度。readOnlyRootFilesystemallowPrivilegeEscalation: false 限制容器权限;requests/limits 固定调度保底和上限;NODE_NAMEspec.nodeName 注入。四个卷依次提供配置、只读宿主机容器日志、TLS CA 与可写的临时注册表目录,emptyDir 随 Pod 删除,不保存业务日志。

配置作用:在获批节点上以最小权限和完整 TLS 校验采集容器日志,并附加 Kubernetes 元数据。 生效结果:首次只在 wk01 出现一个 Filebeat Pod;只有日志采集状态写入 emptyDir,宿主机日志和配置均为只读挂载。 注意事项:扩大范围时一次只给一个节点打标签并完成 Discover 验收;若要跨 Pod 保留 Filebeat 读取位置,必须另行评审持久化注册表,不能把 emptyDir 改为任意业务 PVC。

# 先由 API Server 校验 Filebeat 配置、RBAC 引用与 DaemonSet 字段。
kubectl apply --dry-run=server -f 32-filebeat.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
# 部署后仍因 nodeSelector 没有匹配节点而不会立刻采集。
kubectl apply -f 32-filebeat.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
# 只让 wk01 进入灰度采集范围。
kubectl label node wk01 logs.ops.internal/filebeat=enabled # 仅修改指定节点的采集标签,控制 Filebeat 灰度范围。
# 等待每节点采集器就绪。
kubectl -n observability rollout status daemonset/filebeat --timeout=180s # 等待 wk01 的唯一 Filebeat Pod 就绪。
# 查看 Filebeat 和日志。
kubectl -n observability get pod -l app=filebeat -o wide # 确认只有 wk01 有 Filebeat Pod。
kubectl -n observability logs daemonset/filebeat --tail=80 # 检查 harvest、publish 与 TLS 成功日志。

预期结果:DaemonSet desired/current/ready 相同;日志没有持续 output error。 通过标准:每个 Worker 有一个 Running Filebeat,业务 Pod 新日志能够在后端索引增长中看到。 失败处理:CrashLoop 查 ConfigMap;无日志查宿主机 /var/log/containers、CRI 格式和业务日志;连接失败查 Logstash Service Endpoint。

8. 真实 Kibana 页面验收

Kibana Discover 真实后台

执行机器:已获授权的运维电脑浏览器;不在 Control Plane、Worker 或 Elasticsearch Pod 内执行网页操作。上图来自本项目网站 http://8.163.60.136:18080/ 的真实 Kibana Discover 页面,不是示意图;它展示了菜单入口、Discover 查询页和 Kubernetes 日志字段。

网页路径:Kibana → Stack Management → Data Views → logs-k8s-prod-* → Discover → 最近 15 分钟。

网页操作:创建 Data View 时填入 logs-k8s-prod-*;时间字段选 @timestamp;Discover 选择最近 15 分钟,筛选 kubernetes.namespace 为 business-prod。 预期结果:Discover 的命中数大于 0,日志列表包含 @timestampkubernetes.namespacekubernetes.pod.namemessage通过标准:看到新的 Pod 日志、@timestamp、namespace、pod 名和 message,筛选 business-prod 后仅显示该命名空间的本次采集日志。 失败处理:看不到时严格按应用 → Filebeat → Logstash → Elasticsearch → Kibana 的顺序逐段验证,不能直接重启全部组件。

真实 Kibana 管理后台:Dev Tools 入口

网页操作(只读验证):进入 Management → Dev Tools,先执行 GET /_cluster/healthGET /_cat/indices/logs-k8s-prod-*?v,确认集群健康和当天索引存在;该图来自你提供的网站课程页面。

真实 Dev Tools Console:查询 ES 索引的 200 OK 结果

8.1 Kibana HTTPS 入口

执行机器:cp01 发布机。Ingress 只暴露 Kibana;Elasticsearch 和 Logstash 仍保持集群内部访问。

# 文件:33-kibana-ingress.yaml
# Kubernetes Ingress API。
apiVersion: networking.k8s.io/v1
# 创建七层 HTTPS 入口。
kind: Ingress
metadata:
  # Ingress 名称。
  name: prod-kibana
  # 日志命名空间。
  namespace: observability
spec:
  # 第 6 节验收过的 IngressClass。
  ingressClassName: nginx
  tls:
    - hosts:
        # 统一参数表中的 Kibana 域名。
        - kibana.ops.internal
      # 企业证书系统创建的 TLS Secret。
      secretName: kibana-example-com-tls
  rules:
    - host: kibana.ops.internal
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                # ECK 自动创建的 Kibana HTTPS Service。
                name: prod-kibana-kb-http
                port:
                  number: 5601

配置详解(33-kibana-ingress.yamlingressClassName: nginx 固定由已验收的 Ingress Controller 接管;TLS 的 hosts、规则 host 和证书 SAN 都必须为 kibana.ops.internalsecretName 指向对应 TLS Secret。path: /Prefix 代理全部 Kibana 路径,后端 prod-kibana-kb-http:5601 是 ECK 创建的集群内部 Service。

配置作用:仅向授权用户提供 Kibana HTTPS 入口。 生效结果:ES 9200 与 Logstash 5044 保持集群内部访问,浏览器流量经 Nginx 到 Kibana Ready Endpoint。 注意事项:证书错误先核对 DNS、SAN 和 Secret;禁止长期以 NodePort 或 LoadBalancer 暴露 ES/Logstash。

# 先用服务器端校验检查 IngressClass、Service 和字段。
kubectl apply --dry-run=server -f 33-kibana-ingress.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
# 创建入口。
kubectl apply -f 33-kibana-ingress.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
# 检查 Ingress 已获得地址、后端 Service 有 Endpoint。
kubectl -n observability get ingress prod-kibana # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。
kubectl -n observability get endpoints prod-kibana-kb-http # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。

预期结果:Ingress 有地址,prod-kibana-kb-http 有 Ready Endpoint;浏览器通过 https://kibana.ops.internal 打开登录页。 通过标准:TLS 证书域名匹配,浏览器无证书错误,ES 9200 和 Logstash 5044 没有公网暴露。 失败处理:404 查 IngressClass/host/path;502 查 Endpoints/Kibana Ready;证书错误查 TLS Secret 与 DNS;禁止改成长期 NodePort。

9. 日志断流与安全回滚

执行机器:cp01 发布机;仅在变更回退获批后执行。先停止 Filebeat,再检查业务基线;禁止把回滚范围扩大到业务 Namespace、PVC 或已有采集器。

顺序执行位置检查通过结果失败处理
1业务 Podkubectl logs有新日志应用没有输出或日志级别不对
2Filebeatkubectl logs daemonset/filebeat有 harvest/publish查日志路径、ConfigMap、Service
3Logstashkubectl logs deploy/logstash有接收与输出查 Service、Pipeline、ES 连接
4ESkubectl get elasticsearchhealth green查 PVC、资源、ECK Events
5KibanaDiscover有新 @timestamp查 Data View、时间范围、筛选
# 只删除本手册创建的采集组件,业务 Pod 不会被删除。
kubectl -n observability delete -f 32-filebeat.yaml # 仅删除本手册创建的 Filebeat 资源,业务工作负载不受影响。
# 验证业务未受影响。
kubectl -n business-prod get pod # 核对业务 Pod 数量与 Ready 状态是否保持回滚前基线。

预期结果:Filebeat 被移除,business-prod 的 Pod 数量与状态不变。 通过标准:删除后 60 秒内 Filebeat 不再产生新输出,business-prod 的 Ready Pod 数量、入口探活和错误率与删除前基线一致。 失败处理:禁止删除业务 Namespace、业务 PVC 或已有团队的采集器。

10. 索引生命周期、TLS 与凭据检查

执行机器:cp01 发布机与 ES 管理员。生产中 Logstash 到 ES、Filebeat 到 Logstash 必须使用 TLS;账号和 CA 只能保存在 Secret/keystore,不写入 ConfigMap 或 Git。

# 查看 ECK 自动生成的 ES HTTP CA 和 elastic 用户凭据 Secret。
kubectl -n observability get secret | grep -E 'prod-logs.*(http|user)' # 读取组件最近日志,定位采集、TLS 或输出异常。
# 从 ECK ES Service 内检查 HTTPS 是否可访问。
kubectl -n observability get svc | grep prod-logs # 读取组件最近日志,定位采集、TLS 或输出异常。
# 查看 ES 集群健康。
kubectl -n observability get elasticsearch prod-logs # 读取组件最近日志,定位采集、TLS 或输出异常。

预期结果:存在 HTTP CA 与凭据 Secret,ES health 为 green。 通过标准:Filebeat/Logstash 配置引用 Secret 或 keystore,不包含明文密码;所有跨组件链路使用 TLS。 失败处理:缺 Secret、ES 非 green、发现明文密码时停止发布,先修凭据和证书链。

10.1 ILM 和索引模板

执行机器:ES 管理员终端。以下通过临时端口转发访问 ECK 管理的 HTTPS API;端口转发窗口结束后自动失效。

# 终端 A:转发 ES HTTPS Service 到本机 9200。
kubectl -n observability port-forward service/prod-logs-es-http 9200:9200 # 读取组件最近日志,定位采集、TLS 或输出异常。
# 终端 B:导出 CA 和管理密码到当前会话变量,不回显密码。
kubectl -n observability get secret prod-logs-es-http-ca-internal -o jsonpath='{.data.ca\.crt}' | base64 -d > ./prod-logs-ca.crt # 读取组件最近日志,定位采集、TLS 或输出异常。
read -s -p 'elastic password: ' ELASTIC_PASSWORD; echo # 静默读取当前会话密码,不回显且不写入命令历史。
# 创建 30 天后删除的生命周期策略;本手册的 Logstash 按天写入普通索引,因此不使用需要 rollover alias 的 rollover 动作。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" -X PUT https://127.0.0.1:9200/_ilm/policy/logs-k8s-prod-30d -H 'Content-Type: application/json' -d '{"policy":{"phases":{"hot":{"actions":{}}},"delete":{"min_age":"30d","actions":{"delete":{}}}}}}' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
# 创建 1 主 1 副本索引模板。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" -X PUT https://127.0.0.1:9200/_index_template/logs-k8s-prod-template -H 'Content-Type: application/json' -d '{"index_patterns":["logs-k8s-prod-*"],"template":{"settings":{"number_of_shards":1,"number_of_replicas":1,"index.lifecycle.name":"logs-k8s-prod-30d"}},"priority":500}' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。

预期结果:两个 API 返回 acknowledged:true通过标准:新日志索引匹配 logs-k8s-prod-*,具有 1 主 1 副本和 30 天删除型 ILM。 失败处理:400 查 JSON;403 由 ES 管理员修复权限;不要因为失败删除历史索引。

11. Elasticsearch 备份与恢复演练

执行机器:cp01 发布机与已批准的快照存储管理员。生产备份不是“任务成功”就完成,必须恢复到隔离命名空间验证。

11.0 先创建集群内共享快照仓库

执行机器:cp01。此手册不再假设外部 prod-logs-s3 已经存在;快照目录使用 Rook-CephFS 的 ReadWriteMany 卷,使三个 Elasticsearch Pod 都能访问同一个 /mnt/es-snapshots 目录。此步骤只能在第 6.1 节 Rook-Ceph 已 Ready 后执行。

# 文件:34-es-snapshot-storage.yaml
# 创建 CephFS 文件系统。
apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
  name: elastic-snapshots
  namespace: rook-ceph
spec:
  metadataPool:
    replicated:
      size: 3
  dataPools:
    - name: data0
      replicated:
        size: 3
  metadataServer:
    activeCount: 2
    activeStandby: true
---
# 创建可被多个 ES Pod 同时挂载的 CephFS StorageClass。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cephfs-rwx
provisioner: rook-ceph.cephfs.csi.ceph.com
parameters:
  clusterID: rook-ceph
  fsName: elastic-snapshots
  pool: elastic-snapshots-data0
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: Immediate
---
# 为 ES 快照目录申请独立的共享 PVC。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: es-snapshot-rwx
  namespace: observability
spec:
  accessModes: [ ReadWriteMany ]
  storageClassName: cephfs-rwx
  resources:
    requests:
      storage: 500Gi

配置详解(34-es-snapshot-storage.yaml:CephFilesystem 使用三副本 metadata/data pool 和双 MDS 提供共享文件系统高可用。cephfs-rwx 通过 CephFS CSI 供应 RWX 卷,CSI Secret 固定在 rook-cephes-snapshot-rwx 是独立 500Gi ReadWriteMany PVC,供三台 ES 同时挂载 /mnt/es-snapshots,只存放快照,不承载 ES 数据。

配置作用:为所有 ES 节点提供一致的共享快照目录。 生效结果:注册 fs 快照仓库后,任何 ES 数据节点都能创建快照并按 restore- 前缀隔离恢复。 注意事项:禁止把快照目录放进 ES 数据 PVC、宿主机目录或未验证共享盘。

# 校验并创建 CephFS、StorageClass 和共享快照 PVC。
kubectl apply --dry-run=server -f 34-es-snapshot-storage.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
kubectl apply -f 34-es-snapshot-storage.yaml # 由 Kubernetes API 校验或创建本清单中的资源。
kubectl -n rook-ceph wait --for=condition=Ready cephfilesystem/elastic-snapshots --timeout=30m # 等待目标资源达到就绪状态,超时则停止后续操作。
kubectl -n observability get pvc es-snapshot-rwx # 执行本步骤的固定命令;结果按紧随本代码块的通过标准验证。

真实输出样例es-snapshot-rwx 状态为 Bound,容量为 500Gi,访问模式为 RWX通过标准:CephFilesystem Ready,PVC Bound;任何一个 ES Pod 都能挂载同一共享目录。 失败处理:PVC Pending 查 CephFilesystem、CSI Pod 和容量;禁止将快照目录临时改到任一 ES 数据 PVC 或宿主机本地目录。

# 终端 A:仅在维护窗口内临时访问 ES HTTPS API。
kubectl -n observability port-forward service/prod-logs-es-http 9200:9200 # 读取组件最近日志,定位采集、TLS 或输出异常。
# 终端 B:取出 CA;密码只存于当前 shell 变量。
kubectl -n observability get secret prod-logs-es-http-ca-internal -o jsonpath='{.data.ca\.crt}' | base64 -d > ./prod-logs-ca.crt # 读取组件最近日志,定位采集、TLS 或输出异常。
read -s -p 'elastic password: ' ELASTIC_PASSWORD; echo # 静默读取当前会话密码,不回显且不写入命令历史。
# 注册所有 ES 节点均可访问的 CephFS 文件系统快照仓库。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" -X PUT https://127.0.0.1:9200/_snapshot/prod-logs-cephfs -H 'Content-Type: application/json' -d '{"type":"fs","settings":{"location":"/mnt/es-snapshots","compress":true}}' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
# 查看健康与已注册的 CephFS 快照仓库。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" https://127.0.0.1:9200/_cluster/health?pretty # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" https://127.0.0.1:9200/_snapshot/prod-logs-cephfs?pretty # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
# 创建带日期的快照;wait_for_completion 确保返回时可验收。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" -X PUT 'https://127.0.0.1:9200/_snapshot/prod-logs-cephfs/logs-k8s-prod-20260728?wait_for_completion=true' -H 'Content-Type: application/json' -d '{"indices":"logs-k8s-prod-*","include_global_state":false}' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
# 列出快照状态。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" 'https://127.0.0.1:9200/_snapshot/prod-logs-cephfs/_all?pretty' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
# 清理维护窗口的临时 CA 文件和会话密码。
rm -f ./prod-logs-ca.crt # 清理当前发布机的临时文件或敏感会话变量。
unset ELASTIC_PASSWORD # 清理当前发布机的临时文件或敏感会话变量。

预期结果:ES 为 green,三个数据 PVC Bound。 通过标准:健康 API 返回 green,快照返回 SUCCESS,且快照仓库、保留周期、恢复目标和负责人均已在变更单确认。 失败处理:ES 非 green、仓库不存在、快照非 SUCCESS 或没有隔离恢复环境时,不执行删除/保留策略变更。

11.1 隔离恢复验证

执行机器:ES 管理员终端。恢复使用新索引前缀,不覆盖生产索引。

# 恢复为 restore- 前缀;禁止直接恢复到原 logs-k8s-prod-* 索引。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" -X POST 'https://127.0.0.1:9200/_snapshot/prod-logs-cephfs/logs-k8s-prod-20260728/_restore' -H 'Content-Type: application/json' -d '{"indices":"logs-k8s-prod-*","rename_pattern":"logs-k8s-prod-(.+)","rename_replacement":"restore-logs-k8s-prod-$1","include_global_state":false}' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
# 等待恢复完成并抽查恢复索引文档数。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" 'https://127.0.0.1:9200/_cat/recovery?v' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
curl --cacert ./prod-logs-ca.crt -u "elastic:$ELASTIC_PASSWORD" 'https://127.0.0.1:9200/_cat/indices/restore-logs-k8s-prod-*?v' # 调用 Elasticsearch HTTPS API,使用当前会话凭据和 CA 校验。
# 清理临时 CA 和会话密码变量。
rm -f ./prod-logs-ca.crt # 清理当前发布机的临时文件或敏感会话变量。
unset ELASTIC_PASSWORD # 清理当前发布机的临时文件或敏感会话变量。

预期结果:恢复索引为 restore-logs-k8s-prod-*,恢复状态完成,文档数可查询。 通过标准:随机检索恢复索引能够看到历史日志,生产 logs-k8s-prod-* 的文档数和名称没有被改变。 失败处理:恢复冲突查索引名和磁盘容量;禁止删除生产索引以给恢复让位。