2025数证杯初赛 2025 团队赛

2025数证杯预选赛服务器hermes

围绕 2025数证杯初赛 的公开复盘与解题记录。

上传者:怪叔叔 发布日期:2026-05-22 72 次阅读

25数证杯预选赛 — 服务器取证 详细解题过程

一、服务器环境概览

别名 hostname 外网IP (ens36) 内网IP (ens33) 角色
server1 master 192.168.100.119 192.168.50.80 K8s Master 控制平面
server2 node1 192.168.100.138 192.168.50.81 K8s Worker 节点
server3 node2 192.168.100.140 192.168.50.82 K8s Worker 节点
server4 localhost.localdomain 192.168.100.234 192.168.50.83 NFS 文件服务器

SSH配置: 端口22, 账号root, 密码123456

架构说明:

  • Kubernetes v1.28.15 集群 + NFS 存储
  • server1: K8s Master (apiserver, etcd, controller-manager, scheduler)
  • server2/3: K8s Worker 节点
  • server4: NFS 共享 /data/k8s_data *(rw,no_root_squash)
  • 所有节点均双网卡:ens33=内网集群通信, ens36=外网管理

K8s 配置关键信息:

  • API Server: https://192.168.50.80:6443
  • Pod CIDR: 10.224.0.0/16
  • Service CIDR: 10.96.0.0/12
  • CNI: Calico v3.25.0
  • 镜像仓库: registry.aliyuncs.com/google_containers

二、详细答题记录

Q1. node1节点的磁盘设备SHA256值前六位?(字母全大写)

答案: FC9A34

解题思路:

  • node1 = server2 (hostname: node1)
  • 磁盘设备为 /dev/sda (40G 物理磁盘)
  • 对磁盘设备计算 SHA256 哈希取前6位大写

重点命令:

lsblk -o NAME,SIZE,TYPE,MOUNTPOINT   # 确认磁盘设备为 /dev/sda
sha256sum /dev/sda                    # 输出 fc9a34... → FC9A34

Q2. 集群配置了多少个node节点?

答案: 2

解题思路:

  • server1 (master) = 控制平面节点
  • server2 (node1) = worker 节点
  • server3 (node2) = worker 节点
  • 共 2 个 worker node

重点命令:

hostname                              # 确认各节点角色
ls /etc/kubernetes/manifests/         # master: apiserver, etcd, controller, scheduler
systemctl status kubelet              # 所有节点均有 kubelet

Q3. 嫌疑人于什么时间修改master节点的root密码?(格式: 00:00:00)

答案: 09:35:59

解题思路:

  • master = server1 (hostname: master)
  • 审计日志 /var/log/audit/audit.log 记录 USER_CHAUTHTOK 事件
  • 时间戳 1758245759 → 本地时间 09:35:59 CST
  • bash_history 中也有 passwd 命令记录

重点命令:

grep 'chauthtok' /var/log/audit/audit.log
# type=USER_CHAUTHTOK msg=audit(1758245759.679:190):
# acct="root" exe="/usr/bin/passwd" terminal=tty1 res=success

date -d @1758245759 "+%H:%M:%S"       # 输出 09:35:59

cat /root/.bash_history | grep passwd  # 确认执行了 passwd

Q4. Docker的安装日期是?(格式: 01月01日)

答案: 04月08日

解题思路:

  • RPM 数据库记录了精确的安装时间

重点命令:

rpm -qi docker-ce | grep 'Install Date'
# Install Date: 2025年04月08日 星期二 20时24分20秒

Q5. Docker通过配置守护进程以使用全局代理,该代理地址的端口是?

答案: 4780

解题思路:

  • Docker 全局代理配置在 systemd 服务文件中
  • /usr/lib/systemd/system/docker.service 定义了 HTTP_PROXY/HTTPS_PROXY 环境变量

重点命令:

cat /usr/lib/systemd/system/docker.service | grep -iE 'proxy|http'
# Environment="HTTP_PROXY=192.168.0.99:4780"
# Environment="HTTPS_PROXY=192.168.0.99:4780"
# 代理端口: 4780

Q6. MySQL数据库对外访问的端口是?

答案: 30627

解题思路:

  • 通过独角数卡 .env 配置可知 DB_PORT=30627
  • kubectl get svc mysql 确认 NodePort 为 30627,容器端口 3306 映射到宿主机 30627

重点命令:

kubectl get svc mysql
# NAME   TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
# mysql  NodePort   10.96.x.x      <none>        3306:30627/TCP   ...

Q7. 发卡网站部署的镜像名称是?

答案: webdevops/php-nginx:7.4

解题思路:

  • 查看 php-nginx Deployment 的 YAML 配置,或通过 kubectl 查询 deployment 详情

重点命令:

kubectl get deployment php-nginx -o yaml | grep image
# 或查看 server1 /root/php-nginx-deployment.yaml
cat /root/php-nginx-deployment.yaml | grep image
# image: webdevops/php-nginx:7.4

Q8. Telegram群管机器人容器ID前六位?

答案: 8fadf5

解题思路:

  • 关键点: K8s Pod重启/重调度后容器ID会变化,运行时查询 docker ps 不可靠
  • 必须通过静态分析磁盘镜像取证:加载检材03磁盘镜像,解析Docker/containerd数据获取案发时的容器ID
  • 从磁盘镜像中找到 captcha-bot 对应的容器ID

重点命令:

# 静态分析方式(加载检材03磁盘镜像后)
# 解析 Docker 容器元数据或 containerd 快照数据
# 找到 captcha-bot 容器的原始ID,取前6位: 8fadf5

Q9. 发卡网站使用的缓存数据库是?

答案: redis

解题思路:

  • 独角数卡 .env 配置中 REDIS_HOST=192.168.50.80, REDIS_PORT=31678
  • Laravel 应用使用 Redis 作为缓存后端,后台系统设置也缓存到 Redis

重点命令:

cat /data/k8s_data/default/dujiaoka/.env | grep REDIS
# REDIS_HOST=192.168.50.80
# REDIS_PORT=31678

Q10. 发卡网站代码的物理目录是?

答案: /data/k8s_data/default/dujiaoka

解题思路:

  • php-nginx Deployment 挂载 PVC dujiaoka 到容器内 /app
  • PVC dujiaoka 使用 NFS storageClass,映射到 NFS 服务器 /data/k8s_data/default/dujiaoka/
  • 该目录为 Laravel 项目(独角数卡)源码

重点命令:

# 查看PVC配置
kubectl get pvc dujiaoka -o yaml
# storageClassName: nfs-client

# NFS服务器上的实际路径
ls /data/k8s_data/default/dujiaoka/
# Laravel项目目录结构

Q11. Telegram API代理域名是?

答案: kk.xilika.cc

解题思路:

  • captcha-bot 的配置文件 config.toml 中设置了 Telegram API 代理
  • 该文件位于 captcha-bot PVC 挂载目录或 ConfigMap 中

重点命令:

# 查看 captcha-bot 配置
grep api_proxy /data/k8s_data/default/captchaBot/config.toml
# api_proxy = "http://kk.xilika.cc"
# 代理域名: kk.xilika.cc

Q12. Telegram群名称?

答案: 西门庆交流群

解题思路:

  • captcha-bot 验证码机器人记录在 MySQL captchabot 库的 user_captcha_record 表中
  • 该表有 telegram_chat_name 字段,存储了Telegram群名称

重点命令:

kubectl exec mysql80-5fc9f7b6f7-kjgs2 -- mysql -u root -pLKKD23mjakl213dmmm --default-character-set=utf8mb4
USE captchabot;
SHOW TABLES;                    # advertise, user_captcha_record
DESCRIBE user_captcha_record;   # 有 telegram_chat_name 字段
SELECT DISTINCT telegram_chat_name FROM user_captcha_record;
# → 西门庆交流群

Q13. 统计嫌疑人在Telegram上创建的群中2025年6月之后成功入群的人数为?

答案: 2422

解题思路:

  • "2025年6月之后"包含6月,即 >= '2025-06-01'
  • user_captcha_record 表中 captcha_status=1 表示验证成功(有 captcha_success_time),status=2 表示失败
  • "人数"需要按 telegram_user_id 去重计数

重点命令:

-- 确认status含义: 1=成功(有success_time), 2=失败
SELECT captcha_status, captcha_success_time IS NOT NULL, COUNT(*) 
FROM user_captcha_record GROUP BY captcha_status;
# status=1: 5716条(全有success_time), status=2: 2326条

-- 统计2025年6月之后(含6月)成功入群的唯一用户数
SELECT COUNT(DISTINCT telegram_user_id) 
FROM user_captcha_record 
WHERE captcha_status = 1 
  AND captcha_success_time >= '2025-06-01'
  AND deleted_at IS NULL;
# 结果: 2422

Q14. 据嫌疑人交代曾在发卡网上删除过一条订单数据,请找出该删除订单的订单号是?

答案: 4V8XNK8Q0wMD5D2R

解题思路:

  1. MySQL开启了binlog,但由于expire_logs_seconds设置,仿真启动MySQL容器后会自动删除过期的binlog文件
  2. 但binlog.000002文件本身仍然存在于NFS磁盘上(/data/k8s_data/default/mysql80/binlog.000002),只是已从MySQL活跃日志列表中清除
  3. 需要使用mysqlbinlog工具解析binlog.000002(注意是000002而不是000001)
  4. binlog格式为ROW模式,需用--base64-output=DECODE-ROWS -vv解码为可读SQL
  5. 搜索"DELETE FROM"找到删除的订单数据,提取order_sn

重点命令:

# WSL Ubuntu安装8.0版本的mysqlbinlog(也可本地安装Windows版MySQL 8.0)
apt-get update
apt-get install mysql-client mysql-server-core-8.0 -y

# 从NFS下载binlog文件到本地
# /data/k8s_data/default/mysql80/binlog.000002

# 解析binlog为可读SQL(注意是binlog.000002)
mysqlbinlog --no-defaults -vv --base64-output=DECODE-ROWS /mnt/d/binlog.000002 > bin2.sql

# 搜索DELETE语句找到被删除的订单,提取order_sn
grep "DELETE FROM" bin2.sql -A 20 && rm bin2.sql
# 从中提取 order_sn = 4V8XNK8Q0wMD5D2R

Q15. 发卡网站上2025年6月之后订单交易成功的总金额是?忽略被删除的数据(答案格式:1)

答案: 295202

解题思路:

  • 查询 dujiaoka 库的 orders 表
  • 订单状态定义: STATUS_COMPLETED = 4 (交易成功), STATUS_EXPIRED = -1 (过期)
  • "2025年6月之后"含6月: created_at >= '2025-06-01'
  • "忽略被删除的数据": deleted_at IS NULL
  • 统计交易成功的实际支付金额总和: SUM(actual_price)

重点命令:

# 确认订单状态定义
kubectl exec php-nginx-... -- grep -E "const|STATUS" /app/app/Models/Order.php
# STATUS_COMPLETED = 4 (交易成功)
# STATUS_EXPIRED = -1 (过期)

# 统计2025年6月之后(含6月)订单交易成功的总金额,忽略已删除数据
kubectl exec mysql80-... -- mysql -u root -pLKKD23mjakl213dmmm --default-character-set=utf8mb4
USE dujiaoka;
SELECT SUM(actual_price) 
FROM orders 
WHERE status = 4 
  AND created_at >= '2025-06-01' 
  AND deleted_at IS NULL;
# 结果: 295202.00 → 答案: 295202

Q16. 发卡网站的后台访问路径是?(答案格式:/root)

答案: /admin

解题思路:

  • 独角数卡(Laravel)的后台访问路径由 .env 中的 ADMIN_ROUTE_PREFIX 配置决定
  • 配置文件位于 NFS 挂载的 /data/k8s_data/default/dujiaoka/.env

重点命令:

cat /data/k8s_data/default/dujiaoka/.env | grep ADMIN_ROUTE_PREFIX
# ADMIN_ROUTE_PREFIX=/admin

Q17. 密码哈希中使用的自定义salt的base64编码值为?

答案: lAID2ktDeRlGbcg=

解题思路:

  1. 查看 vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php,发现重写了 getSalt() 方法
  2. 该自定义 salt 在 make()check() 中都会被追加到密码后面再做 bcrypt 哈希
  3. Salt 生成算法:
protected function getSalt()
{
    $a = 'sdahjklhl212jkljass';
    $b = hash('sha256', $a, true);       // SHA256二进制
    $c = substr($b, 0, 16);              // 取前16字节
    $d = base64_decode('xPfGJQaE1zE5d+8='); // 解码11字节
    $e = '';
    for($i=0; $i<strlen($d); $i++) {
        $e .= chr(ord($d[$i]) ^ ord($c[$i])); // 逐字节XOR
    }
    return $e;
}
  1. 计算过程:
    • SHA256('sdahjklhl212jkljass') → 取前16字节
    • base64_decode('xPfGJQaE1zE5d+8=') → 11字节
    • 两者逐字节 XOR → 11字节结果
    • Base64编码 → lAID2ktDeRlGbcg=

重点命令:

kubectl exec php-nginx-... -- php -r "
\$a = 'sdahjklhl212jkljass';
\$b = hash('sha256', \$a, true);
\$c = substr(\$b, 0, 16);
\$d = base64_decode('xPfGJQaE1zE5d+8=');
\$e = '';
for(\$i=0; \$i<strlen(\$d); \$i++) {
    \$e .= chr(ord(\$d[\$i]) ^ ord(\$c[\$i]));
}
echo base64_encode(\$e) . PHP_EOL;
// → lAID2ktDeRlGbcg=
"

Q18. 发卡网站配置的邮件发送人地址是?(答案格式:abc@abc.com

答案: ituzz@qq.com

解题思路:

  1. 独角数卡的邮件配置不在 .env 中,而是存在后台系统设置表里,运行时缓存到 Redis 的 system-setting key
  2. 代码入口: app/Jobs/MailSend.php 第67行: $sysConfig['from_address']
  3. 配置来源: app/Admin/Forms/SystemSetting.php 第83行: $this->text('from_address'),保存时写入 Cache::put('system-setting', $input)
  4. 直接从Redis缓存读取即可获取完整邮件配置

重点命令:

# 在php-nginx容器中通过artisan tinker读取Redis缓存的系统设置
kubectl exec php-nginx-... -- php /app/artisan tinker --execute="echo json_encode(cache('system-setting'));\"

# 返回结果中包含邮件相关配置:
{
  "driver": "smtp",
  "host": "smtp.qq.com",
  "port": "587",
  "username": "ituzz@qq.com",
  "password": "hxobygicmubhddhb",
  "encryption": "tls",
  "from_address": "ituzz@qq.com",
  "from_name": "【独角数卡-自助发卡】"
}

# from_address = ituzz@qq.com

Q19. 当前发卡网站首页仪表盘中显示的发卡网站版本为?(答案格式:1.1.1)

答案: 2.0.5

解题思路:

  1. 后台首页仪表盘标题视图: resources/views/admin/dashboard/title.blade.php
  2. 版本显示代码: <h1>独角数卡 V{{ config('dujiaoka.dujiaoka_version', '2.0.0') }}</h1>
  3. 版本号定义在配置文件 config/dujiaoka.php

重点命令:

# 查找版本配置
grep -rn "dujiaoka_version" /app/config/
# /app/config/dujiaoka.php:11:    'dujiaoka_version' => '2.0.5',

# 确认视图中的版本引用
cat /app/resources/views/admin/dashboard/title.blade.php
# 显示: config('dujiaoka.dujiaoka_version', '2.0.0')

Q20. 当前发卡网站中绑定的订单推送Telegram用户id为?

答案: 6213151597

解题思路:

  1. 独角数卡后台系统设置中有"订单推送"选项卡,包含Telegram推送开关和用户ID
  2. 代码: app/Admin/Forms/SystemSetting.php 第64-65行: is_open_telegram_pushtelegram_userid
  3. 配置保存到Redis缓存 system-setting,Q18读取时已经获取到完整配置

重点命令:

# 从之前Q18的system-setting缓存中提取
kubectl exec php-nginx-... -- php /app/artisan tinker --execute="echo json_encode(cache('system-setting'));\"\n

# 相关配置片段:
{
  "is_open_telegram_push": 1,
  "telegram_bot_token": "8378348497:***",
  "telegram_userid": "6213151597"
}

三、关键取证知识点

1. 磁盘设备 SHA256

  • sha256sum /dev/sdX — 对整个磁盘设备计算哈希
  • lsblk — 查看磁盘分区结构

2. K8s 节点识别

  • Master: /etc/kubernetes/manifests/ 包含控制平面组件 YAML
  • Worker: 只有 kubelet 和 docker,无 manifests 目录

3. 密码修改取证

  • 审计日志: /var/log/audit/audit.log → grep chauthtok
  • 时间戳转换: date -d @EPOCH "+%H:%M:%S"
  • bash_history: cat /root/.bash_history | grep passwd

4. RPM 安装时间

  • rpm -qi <package> → Install Date 字段

5. Docker 代理配置

  • systemd 服务文件: /usr/lib/systemd/system/docker.service
  • drop-in 目录: /etc/systemd/system/docker.service.d/
  • daemon.json 通常不含代理配置

6. MySQL binlog 恢复删除数据

  • binlog被自动清理后文件仍存在于磁盘(NFS/PVC挂载目录)
  • ROW格式需 mysqlbinlog --no-defaults -vv --base64-output=DECODE-ROWS 解码
  • 注意是 binlog.000002 而非 000001

7. Redis 运行时配置

  • 独角数卡后台系统设置(邮件、Telegram推送等)运行时缓存到Redis system-setting
  • 通过 artisan tinker 读取: cache('system-setting')

8. 自定义 Bcrypt Salt

  • 源码位于 vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php
  • getSalt() 方法: SHA256哈希前16字节 XOR 固定base64字符串

四、K8s 集群修复过程

第一轮:证书过期修复 (master)

根因: 所有 K8s 组件证书于 2026-04-08 12:27 UTC 过期(有效期1年,集群创建于 2025-04-08)。CA证书未过期(至2035)。

过期证书清单:

证书 过期时间 CA
apiserver.crt Apr 08, 2026 12:27 UTC ca
apiserver-etcd-client.crt Apr 08, 2026 12:27 UTC etcd-ca
apiserver-kubelet-client.crt Apr 08, 2026 12:27 UTC ca
front-proxy-client.crt Apr 08, 2026 12:27 UTC front-proxy-ca
etcd server/peer/healthcheck Apr 08, 2026 12:27 UTC etcd-ca
admin.conf / controller-manager.conf / scheduler.conf Apr 08, 2026 12:27 UTC ca
kubelet-client-2025-04-08.pem Apr 08, 2026 12:27 UTC ca

修复步骤:

# 步骤1: kubeadm 批量续期控制平面证书
kubeadm certs check-expiration          # 检查所有证书过期状态
kubeadm certs renew all                 # 批量续期(不含 kubelet 证书)
# 输出确认 10 个证书已续期,新有效期: 2027-05-16 (364天)

# 步骤2: 手动续期 kubelet 客户端证书
openssl genrsa -out /var/lib/kubelet/pki/kubelet-client.key 2048

openssl req -new -key /var/lib/kubelet/pki/kubelet-client.key \
  -subj "/CN=system:node:master/O=system:nodes" -out /tmp/kubelet.csr

openssl x509 -req -in /tmp/kubelet.csr \
  -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key \
  -CAcreateserial -out /var/lib/kubelet/pki/kubelet-client.crt -days 365

# 合并 cert + key 为单文件(kubelet.conf 中 client-certificate 和 client-key 指向同一 PEM)
cat /var/lib/kubelet/pki/kubelet-client.crt /var/lib/kubelet/pki/kubelet-client.key \
  > /var/lib/kubelet/pki/kubelet-client-2026-05-16.pem

# 更新软链接
ln -sf /var/lib/kubelet/pki/kubelet-client-2026-05-16.pem \
       /var/lib/kubelet/pki/kubelet-client-current.pem

# 步骤3: 清理旧容器并重启
docker rm -f $(docker ps -aq)          # 清理所有已退出容器
systemctl restart docker                # 重启 Docker,静态 Pod manifests 自动拉起控制平面
systemctl restart kubelet               # 重启 kubelet,使用新证书连接 API Server

关键细节: kubelet.conf 配置了 client-certificateclient-key 均指向 kubelet-client-current.pem(同一文件),所以该 PEM 必须同时包含证书和私钥。

修复后验证结果:

  • 节点: master (192.168.50.80), node1 (192.168.50.81), node2 (192.168.50.82) — v1.28.15
  • 组件健康: scheduler / controller-manager / etcd-0 全部 Healthy
  • API Server: https://192.168.50.80:6443 正常监听

第二轮:node2 kubelet 恢复 + NFS 防火墙 (2026-05-16 11:34)

问题1: node2 kubelet NotReady — 三层叠加

错误日志:

E0516 11:31:30.237086 node2 kubelet[28836] "command failed" err="failed to run Kubelet: \
failed to initialize client certificate manager: could not convert data from \
\"/var/lib/kubelet/pki/kubelet-client-current.pem\" into cert/key pair: \
tls: failed to parse private key"

三层根因:

  1. 软链接指向不存在的文件: 文件名命名不一致(无 node2 前缀)
  2. PEM文件格式错误 — 证书与密钥不匹配: openssl rsa -in kubelet-client.key -check → d e not congruent to 1
  3. node2 没有 CA 私钥: worker节点只有 ca.crt,无 ca.key

修复流程:

# 在 node2 (server3) 上执行:
cd /var/lib/kubelet/pki
openssl genrsa -out kubelet-client.key 2048
openssl req -new -key kubelet-client.key \
  -subj "/CN=system:node:node2/O=system:nodes" -out /tmp/kubelet.csr
# CSR关键参数: CN=system:node:node2, O=system:nodes

# 将CSR内容拷贝到master (server1),在master上执行:
cat > /tmp/node2-kubelet.csr << 'EOF'
-----BEGIN CERTIFICATE REQUEST-----
(CSR内容)
-----END CERTIFICATE REQUEST-----
EOF

openssl x509 -req -in /tmp/node2-kubelet.csr \
  -CA /etc/kubernetes/pki/ca.crt \
  -CAkey /etc/kubernetes/pki/ca.key \
  -CAcreateserial -out /tmp/node2-kubelet.crt -days 365

# 验证:
openssl x509 -in /tmp/node2-kubelet.crt -noout -subject -dates
# subject= /CN=system:node:node2/O=system:nodes

# 将签发的证书写回 node2:
cat > /var/lib/kubelet/pki/kubelet-client.crt << 'EOF'
-----BEGIN CERTIFICATE-----
(证书内容)
-----END CERTIFICATE-----
EOF

# 合并证书+密钥为PEM文件 (注意: 先证书后密钥)
cat /var/lib/kubelet/pki/kubelet-client.crt \
    /var/lib/kubelet/pki/kubelet-client.key \
  > /var/lib/kubelet/pki/kubelet-client-node2-2026-05-16.pem

# 验证:
openssl x509 -in /var/lib/kubelet/pki/kubelet-client.crt -noout -subject -dates
openssl rsa -in /var/lib/kubelet/pki/kubelet-client.key -check -noout
# RSA key ok

systemctl restart kubelet

问题2: NFS 防火墙阻挡

症状: captcha-bot Pod 挂载 NFS 卷失败,mount.nfs: No route to host。ping通但 showmount -e 失败 → firewalld 阻挡。

根因: server4 firewalld 只开放了 ssh + dhcpv6-client,NFS相关端口全被阻挡。

修复:

# 在 server4 (192.168.50.83) 上执行:
firewall-cmd --permanent --add-service=nfs
firewall-cmd --permanent --add-service=rpc-bind
firewall-cmd --permanent --add-service=mountd
firewall-cmd --reload

# 验证:
firewall-cmd --list-all
# services: dhcpv6-client mountd nfs rpc-bind ssh

showmount -e 192.168.50.83
# Export list for 192.168.50.83: /data/k8s_data *

第三轮:恢复 MySQL、php-nginx、独角数卡 (2026-05-16 12:00)

背景: 第二轮修复后,MySQL 和 php-nginx 的 Deployment 已被删除,Service 存在但无端点。YAML 配置文件在 server1 /root/ 下。

server1 /root/ 可用配置文件清单:

文件 类型 说明
mysql-d.yaml Deployment mysql80, image=mysql:8.0
mysql80-pvc.yaml PVC NFS存储, 5Gi RWX
php-nginx-deployment.yaml Deployment+Service webdevops/php-nginx:7.4
dujiaoka-c.yaml ConfigMap nginx vhost.conf
dujiaoka-sv.yaml ConfigMap supervisor laravel-worker
dujiaoka-pvc.yaml PVC 已存在, NFS存储 5Gi RWX

恢复步骤:

# 步骤1: 创建 MySQL PVC + Deployment
kubectl apply -f /root/mysql80-pvc.yaml
kubectl apply -f /root/mysql-d.yaml
# 结果: PVC mysql80 Bound, Pod mysql80-5fc9f7b6f7-kjgs2 Running (node2)

# 步骤2: 验证 MySQL Service 端点
kubectl get svc mysql          # NodePort 30627
kubectl get endpoints mysql    # 10.224.104.30:3306 ✓

# 步骤3: 恢复 captcha-bot
kubectl delete pod -l app=captcha-bot --force --grace-period=0
# 新 Pod captcha-bot-6b4d85b765-vq89d → 1/1 Running ✓

# 步骤4: 创建 nginx ConfigMap + php-nginx Deployment
kubectl apply -f /root/dujiaoka-c.yaml
kubectl apply -f /root/php-nginx-deployment.yaml

# 步骤5: 解决镜像调度问题
# webdevops/php-nginx:7.4 只在 master (server1) 上有,Pod 调度到 node2 导致 ContainerCreating
# 解决方案: 在 Deployment YAML 中添加 nodeName: master 强制调度到有镜像的节点

修复后最终状态:

服务 Pod Node Service 端口 HTTP
mysql80 1/1 Running node2 mysql 3306:30627
php-nginx 1/1 Running master php-nginx-service 80:31669 200 ✓
captcha-bot 1/1 Running node2
redis 1/1 Running node1/node2 redis 6379:31678

五、取证规则与经验总结

核心规则

  1. 容器ID动态性: K8s Pod重调度后容器ID变化。静态题用磁盘镜像解析Docker/containerd数据(如Q8),非运行时 docker ps
  2. Kubelet证书: PEM文件必须同时包含证书+私钥(cert在前,key在后)。CN=system:node:<节点名>, O=system:nodes。Worker无ca.key需到master签发。验证密钥: openssl rsa -in key -check -noout → 必须输出 RSA key ok
  3. NFS防火墙: CentOS 7 firewalld默认只开SSH。NFS需三个service: nfs, rpc-bind, mountd。ping通≠NFS通,用 showmount -e 验证RPC端口。修改后必须 firewall-cmd --reload 生效
  4. 内网vs外网: 192.168.50.x = 集群通信(ens33); 192.168.100.x = SSH管理(ens36)
  5. MySQL编码: 中文数据需 --default-character-set=utf8mb4
  6. Binlog恢复: MySQL binlog被自动清理后文件仍存在于磁盘(NFS/PVC挂载目录)。用 mysqlbinlog --no-defaults -vv --base64-output=DECODE-ROWS 解析ROW格式。注意binlog文件编号(通常是000002而非000001)
  7. 配置优先级: PVC挂载 > ConfigMap > Deployment env; 应用运行时修改写回PVC持久化

关键取证命令速查

sha256sum /dev/sda                          # 磁盘哈希
grep 'chauthtok' /var/log/audit/audit.log   # 密码修改审计
date -d @EPOCH "+%H:%M:%S"                  # 时间戳转本地时间(CST)
rpm -qi <pkg> | grep 'Install Date'         # RPM安装时间

kubeadm certs check-expiration              # K8s证书状态
kubeadm certs renew all                     # 批量续期控制平面证书
kubectl get nodes/pods/svc --all-namespaces # 集群状态

docker ps -a --filter "name=<p>" --format "{{.ID}} {{.Names}}"  # 容器ID

# MySQL查询(需utf8mb4编码支持中文)
kubectl exec <mysql-pod> -- mysql -u root -pPASSWORD --default-character-set=utf8mb4

# Redis缓存读取(系统设置、邮件配置等运行时配置)
kubectl exec <php-nginx-pod> -- php /app/artisan tinker --execute="echo json_encode(cache('system-setting'));\"

# Binlog恢复删除数据
mysqlbinlog --no-defaults -vv --base64-output=DECODE-ROWS binlog.000002 > output.sql
grep "DELETE FROM" output.sql -A 20         # 查找被删除的订单

# 自定义Salt计算
php -r "\$a='sdahjklhl212jkljass'; \$b=hash('sha256',\$a,true); \$c=substr(\$b,0,16); \$d=base64_decode('xPfGJQaE1zE5d+8='); \$e=''; for(\$i=0;\$i<strlen(\$d);\$i++) \$e.=chr(ord(\$d[\$i])^ord(\$c[\$i])); echo base64_encode(\$e);"

六、应用架构与配置速查

独角数卡 .env 配置

DB_HOST=192.168.50.80
DB_PORT=30627
DB_DATABASE=dujiaoka
DB_USERNAME=root
DB_PASSWORD=LKKD23mjakl213dmmm
REDIS_HOST=192.168.50.80
REDIS_PORT=31678
APP_URL=http://xillka.com:31669
ADMIN_ROUTE_PREFIX=/admin

应用架构

独角数卡(Laravel) → php-nginx:7.4 (NodePort 31669)
  └─ MySQL 8.0 (NodePort 30627) — root/LKKD23mjakl213dmmm, db=dujiaoka
  └─ Redis (NodePort 31678)

Telegram Bot (captcha-bot) → config.toml (api_proxy=http://kk.xilika.cc)
  └─ MySQL captchabot库 — user_captcha_record表含群名、入群记录

NFS数据 (server4 /data/k8s_data/default/):
  captchaBot/  dujiaoka/  mysql80/  redis/

Q12解题关键路径(Telegram群名称查询)

config.toml (api_proxy=kk.xilika.cc) → 查群名
  → 日志 log_2025091[78].log → 无群名
  → captcha-bot-c.yaml ConfigMap → 消息模板含%s占位符,非硬编码
  → kubectl exec mysql80: mysql -u root -pLKKD23mjakl213dmmm
    → SHOW DATABASES → captchabot
    → SHOW TABLES → advertise, user_captcha_record
    → DESCRIBE user_captcha_record → 有 telegram_chat_name 字段
    → SELECT DISTINCT telegram_chat_name → 西门庆交流群