[{"content":"昇腾 K8s 环境部署指南\r概述\r本文档介绍在 Kubernetes 集群中部署昇腾 NPU 容器化环境的完整流程，适用于以下场景：\n容器运行时：Containerd NPU 设备：Ascend 310P 系统架构：aarch64（ARM64） Kubernetes 版本：1.28+ 部署流程主要包括三个步骤：\n环境准备（节点标签、用户、目录） 安装 Ascend Docker Runtime 部署 Ascend Device Plugin 准备工作\r创建节点标签\r为 Kubernetes 节点添加相应标签，用于后续的 Pod 调度和资源管理。\n1 2 3 4 5 6 7 8 # 标记主节点 kubectl label nodes ecs-b0tf90001 masterselector=dls-master-node # 标记 NPU 计算节点 kubectl label nodes ecs-exyqec0002 node-role.kubernetes.io/worker=worker kubectl label nodes ecs-exyqec0002 workerselector=dls-worker-node kubectl label nodes ecs-exyqec0002 host-arch=huawei-arm kubectl label nodes ecs-exyqec0002 accelerator=huawei-Ascend310P 说明：请根据实际节点名称修改 ecs-b0tf90001 和 ecs-exyqec0002。\n创建系统用户\r在 Ascend 计算节点（如 ecs-exyqec0002）上创建专用用户和用户组。\n1 2 3 4 5 6 7 8 # 创建 hwMindX 用户（UID 9000） useradd -d /home/hwMindX -u 9000 -m -s /sbin/nologin hwMindX # 创建 HwHiAiUser 用户组 groupadd HwHiAiUser # 将 hwMindX 用户添加到 HwHiAiUser 组 usermod -a -G HwHiAiUser hwMindX 注意：UID 9000 和用户组 HwHiAiUser 是昇腾软件栈的默认配置，请勿随意修改。\n创建日志目录\r在 Ascend 计算节点上创建 Device Plugin 日志目录。\n1 2 3 # 创建日志目录并设置权限 mkdir -m 750 /var/log/mindx-dl/devicePlugin chown root:root /var/log/mindx-dl/devicePlugin 安装 Ascend Docker Runtime\rAscend Docker Runtime 是容器化环境下使用昇腾 NPU 的核心组件，需要在所有昇腾计算节点上安装。\n下载安装包\r前往官方 Git 仓库 下载对应版本的安装包。\n示例：Ascend-docker-runtime_7.2.RC1.SPC2_linux-aarch64.run\n安装步骤\r步骤 1：进入安装包目录\r1 cd \u0026lt;path to run package\u0026gt; 步骤 2：校验安装包完整性\r1 ./Ascend-docker-runtime_{version}_linux-{arch}.run --check 预期输出：\n1 2 [WARNING]: --check is meaningless for Ascend-docker-runtime and will be discarded in the future Verifying archive integrity... All good. 步骤 3：添加可执行权限\r1 chmod u+x Ascend-docker-runtime_{version}_linux-{arch}.run 步骤 4：执行安装\r方式一：安装到默认路径（推荐）\n1 ./Ascend-docker-runtime_{version}_linux-{arch}.run --install 方式二：安装到自定义路径\n1 ./Ascend-docker-runtime_{version}_linux-{arch}.run --install --install-path=\u0026lt;path\u0026gt; 安装成功输出示例：\n1 2 3 4 Uncompressing ascend-docker-runtime 100% [INFO]: installing ascend docker runtime ... [INFO] Ascend Docker Runtime install success 默认安装路径：/usr/local/Ascend/Ascend-Docker-Runtime/\n配置 Containerd\r根据系统使用的 cgroup 版本修改 Containerd 配置文件 /etc/containerd/config.toml。\n配置方式一：Cgroup v1\r需要修改以下两个关键配置项：\nruntime_type = \u0026quot;io.containerd.runtime.v1.linux\u0026quot; runtime = \u0026quot;/usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime\u0026quot; 完整配置示例：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes] [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc] runtime_type = \u0026#34;io.containerd.runtime.v1.linux\u0026#34; runtime_engine = \u0026#34;\u0026#34; runtime_root = \u0026#34;\u0026#34; privileged_without_host_devices = false base_runtime_spec = \u0026#34;\u0026#34; [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc.options] [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.cni] bin_dir = \u0026#34;/opt/cni/bin\u0026#34; conf_dir = \u0026#34;/etc/cni/net.d\u0026#34; max_conf_num = 1 conf_template = \u0026#34;\u0026#34; [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.registry] [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.registry.mirrors] [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.registry.mirrors.\u0026#34;docker.io\u0026#34;] endpoint = [\u0026#34;https://registry-1.docker.io\u0026#34;] [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.image_decryption] key_model = \u0026#34;\u0026#34; # ... 其他配置 ... [plugins.\u0026#34;io.containerd.monitor.v1.cgroups\u0026#34;] no_prometheus = false [plugins.\u0026#34;io.containerd.runtime.v1.linux\u0026#34;] shim = \u0026#34;containerd-shim\u0026#34; runtime = \u0026#34;/usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime\u0026#34; runtime_root = \u0026#34;\u0026#34; no_shim = false shim_debug = false [plugins.\u0026#34;io.containerd.runtime.v2.task\u0026#34;] platforms = [\u0026#34;linux/amd64\u0026#34;] 配置方式二：Cgroup v2\r需要修改以下关键配置项：\nBinaryName = \u0026quot;/usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime\u0026quot; 完整配置示例：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.default_runtime.options] [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes] [plugins.\u0026#34;io.containerd.grpc.v2.cri\u0026#34;.containerd.runtimes.runc] base_runtime_spec = \u0026#34;\u0026#34; cni_conf_dir = \u0026#34;\u0026#34; cni_max_conf_num = 0 container_annotations = [] pod_annotations = [] privileged_without_host_devices = false runtime_engine = \u0026#34;\u0026#34; runtime_path = \u0026#34;\u0026#34; runtime_root = \u0026#34;\u0026#34; runtime_type = \u0026#34;io.containerd.runc.v2\u0026#34; [plugins.\u0026#34;io.containerd.grpc.v2.cri\u0026#34;.containerd.runtimes.runc.options] BinaryName = \u0026#34;/usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime\u0026#34; CriuImagePath = \u0026#34;\u0026#34; CriuPath = \u0026#34;\u0026#34; CriuWorkPath = \u0026#34;\u0026#34; IoGid = 0 IoUid = 0 NoNewKeyring = false NoPivotRoot = false Root = \u0026#34;\u0026#34; ShimCgroup = \u0026#34;\u0026#34; SystemdCgroup = true 提示：查看 cgroup 版本可执行 stat -fc %T /sys/fs/cgroup/，输出 cgroup2fs 表示 v2，输出 tmpfs 表示 v1。\n重启服务\r1 2 systemctl daemon-reload systemctl restart containerd kubelet 验证安装\r在 Kubernetes 主节点执行以下命令，确认昇腾计算节点状态正常：\n1 kubectl get nodes 预期输出示例：\n1 2 3 NAME STATUS ROLES AGE VERSION k8s-master Ready master,worker 3d v1.28.12 k8s-worker Ready worker 3d v1.28.12 所有节点状态应显示为 Ready。\n部署 Ascend Device Plugin\rAscend Device Plugin 负责在 Kubernetes 中管理和分配 NPU 资源。\n准备镜像\r1. 拉取镜像\n在昇腾计算节点上执行以下命令拉取所需镜像：\n1 2 3 4 5 6 7 8 9 # 拉取所有必需镜像 docker pull --platform=arm64 swr.cn-south-1.myhuaweicloud.com/ascendhub/resilience-controller:v7.1.RC1 docker pull --platform=arm64 swr.cn-south-1.myhuaweicloud.com/ascendhub/ascend-operator:v7.2.RC1 docker pull --platform=arm64 swr.cn-south-1.myhuaweicloud.com/ascendhub/npu-exporter:v7.2.RC1 docker pull --platform=arm64 swr.cn-south-1.myhuaweicloud.com/ascendhub/ascend-k8sdeviceplugin:v7.2.RC1 docker pull --platform=arm64 swr.cn-south-1.myhuaweicloud.com/ascendhub/vc-controller-manager:v1.7.0-v7.2.RC1 docker pull --platform=arm64 swr.cn-south-1.myhuaweicloud.com/ascendhub/vc-scheduler:v1.7.0-v7.2.RC1 docker pull --platform=arm64 swr.cn-south-1.myhuaweicloud.com/ascendhub/noded:v7.2.RC1 docker pull --platform=arm64 swr.cn-south-1.myhuaweicloud.com/ascendhub/clusterd:v7.2.RC1 2. 导出镜像\n1 2 3 4 5 6 7 8 9 docker save -o ascend.tar \\ swr.cn-south-1.myhuaweicloud.com/ascendhub/resilience-controller:v7.1.RC1 \\ swr.cn-south-1.myhuaweicloud.com/ascendhub/ascend-operator:v7.2.RC1 \\ swr.cn-south-1.myhuaweicloud.com/ascendhub/npu-exporter:v7.2.RC1 \\ swr.cn-south-1.myhuaweicloud.com/ascendhub/ascend-k8sdeviceplugin:v7.2.RC1 \\ swr.cn-south-1.myhuaweicloud.com/ascendhub/vc-controller-manager:v1.7.0-v7.2.RC1 \\ swr.cn-south-1.myhuaweicloud.com/ascendhub/vc-scheduler:v1.7.0-v7.2.RC1 \\ swr.cn-south-1.myhuaweicloud.com/ascendhub/noded:v7.2.RC1 \\ swr.cn-south-1.myhuaweicloud.com/ascendhub/clusterd:v7.2.RC1 3. 导入到 Containerd\n1 ctr -n k8s.io images import ascend.tar 下载部署配置文件\r前往官方 Git 仓库 下载 Ascend Device Plugin 安装包。\n示例：Ascend-mindxdl-device-plugin_7.2.RC1.SPC2_linux-aarch64.zip\n解压后将对应的 YAML 文件拷贝到 Kubernetes 管理节点。\n选择合适的 YAML 文件\r根据实际设备类型和是否使用 Volcano 调度器选择对应的 YAML 文件：\nYAML 文件名称 适用场景 device-plugin-310-v{version}.yaml Atlas 300I 推理卡，不使用 Volcano device-plugin-310-volcano-v{version}.yaml Atlas 300I 推理卡，使用 Volcano device-plugin-310P-1usoc-v{version}.yaml Atlas 200I SoC A1 核心板，不使用 Volcano device-plugin-310P-1usoc-volcano-v{version}.yaml Atlas 200I SoC A1 核心板，使用 Volcano device-plugin-310P-v{version}.yaml Atlas 推理系列产品（如 310P），不使用 Volcano device-plugin-310P-volcano-v{version}.yaml Atlas 推理系列产品，使用 Volcano device-plugin-910-v{version}.yaml Atlas 训练系列产品/A2/A3/800I A2，不使用 Volcano device-plugin-volcano-v{version}.yaml Atlas 训练系列产品/A2/A3/800I A2，使用 Volcano 注意：\n对于 Ascend 310P 设备，通常选择 device-plugin-310P-v{version}.yaml 请勿修改 YAML 文件中的 DaemonSet.metadata.name 字段，以免自动识别功能异常 部署 Device Plugin\r1 2 # 请修改镜像名称为：swr.cn-south-1.myhuaweicloud.com/ascendhub/ascend-k8sdeviceplugin:v7.2.RC1 kubectl apply -f device-plugin-310P-v7.2.RC1.SPC2.yaml 预期输出：\n1 2 3 4 serviceaccount/ascend-device-plugin-sa created clusterrole.rbac.authorization.k8s.io/pods-node-ascend-device-plugin-role created clusterrolebinding.rbac.authorization.k8s.io/pods-node-ascend-device-plugin-rolebinding created daemonset.apps/ascend-device-plugin-daemonset created 验证部署\r查看 Device Plugin 是否启动成功：\n1 kubectl get pod -n kube-system | grep ascend 预期输出（状态应为 Running）：\n1 2 NAME READY STATUS RESTARTS AGE ascend-device-plugin-daemonset-d5ctz 1/1 Running 0 11s 查看节点 NPU 资源：\n1 kubectl describe node \u0026lt;node-name\u0026gt; | grep -A 5 \u0026#34;Capacity:\u0026#34; 应能看到 huawei.com/Ascend310P 资源。\n使用 NPU 计算卡\rPod 配置示例\r在 Pod 定义中通过 resources 字段申请 NPU 资源：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: v1 kind: Pod metadata: name: npu-test-pod spec: containers: - name: alg-container image: ubuntu:22.04 resources: limits: memory: 24Gi huawei.com/Ascend310P: 1 # 申请 1 张 NPU 卡 requests: memory: 2Gi huawei.com/Ascend310P: 1 # 申请 1 张 NPU 卡 command: [\u0026#34;/bin/bash\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;sleep infinity\u0026#34;] 说明：\nhuawei.com/Ascend310P 的值表示申请的 NPU 卡数量 limits 和 requests 中的值应保持一致 根据实际 NPU 型号调整资源名称（如 310、910 等） ","date":"2026-01-08T00:00:00Z","image":"/p/ascend_on_k8s/ascend_on_k8s.png","permalink":"/p/ascend_on_k8s/","title":"昇腾 K8s 环境部署指南"},{"content":"问题背景\r在服务器运维过程中，遇到了一个棘手的问题：向服务器插入了一块新的 1TB SAS 硬盘后，使用 lsblk 命令无法看到这块新硬盘。经过排查发现，这块硬盘虽然已经被 RAID 卡识别，但处于 Unconfigured(good) 状态，没有被配置为虚拟磁盘，因此操作系统无法访问。\n本文记录了使用 MegaCli 工具排查和解决此问题的完整过程。\n安装 MegaCli 工具\rMegaCli 是用于管理 LSI/Broadcom RAID 卡的命令行工具，在 Ubuntu/Debian 系统上的安装步骤如下：\n下载 MegaCli 软件包\r1 wget https://docs.broadcom.com/docs-and-downloads/raid-controllers/raid-controllers-common-files/8-07-14_MegaCLI.zip 解压软件包\r1 unzip 8-07-14_MegaCLI.zip 转换 RPM 包为 DEB 包\r由于下载的是 RPM 格式，需要使用 alien 工具转换为 DEB 格式：\n1 2 cd Linux sudo alien MegaCli-8.07.14-1.noarch.rpm 安装 DEB 包\r1 sudo dpkg -i megacli_8.07.14-2_all.deb 修复依赖库问题（如需要）\r如果遇到缺少 libncurses.so.5 的错误，创建符号链接：\n1 sudo ln -s /usr/lib/x86_64-linux-gnu/libncurses.so.6 /usr/lib/x86_64-linux-gnu/libncurses.so.5 验证安装\r1 /opt/MegaRAID/MegaCli/MegaCli64 -v 查看 RAID 状态\r使用以下命令查看所有物理硬盘的详细信息：\n1 /opt/MegaRAID/MegaCli/MegaCli64 -PDList -aALL 通过 MegaCli 的输出，可以清楚地看到服务器上所有硬盘的当前状态。\n排查结论\r服务器共有 7 块物理硬盘，新插入的硬盘位于 Slot 6，当前状态为 Unconfigured(good)（未配置，状态良好），还没有被配置成虚拟磁盘（Virtual Drive），因此 Linux 操作系统（lsblk）无法识别它。\n硬盘分布详细解读\r为了便于理解，将 7 块硬盘按用途分为 4 组：\n系统盘（对应 /dev/sda）\r位置：Slot 5 型号：Intel 120GB SSD 状态：Online 配置：单盘 RAID 0（或直通模式） 用途：系统启动盘 数据盘阵列（对应 /dev/sdb，容量 2.7TB）\r位置：Slot 0, 1, 2, 3 型号：4 块 Seagate 1TB SAS 硬盘 状态：Online 配置：RAID 5 阵列（3块数据 + 1块校验 = 3TB 可用容量） 数据盘（对应 /dev/sdc，容量 931GB）\r位置：Slot 7 型号：Seagate 1TB SAS 硬盘 状态：Online 配置：单盘 RAID 0 🔍 关键发现：新插入的硬盘（Slot 6）\r位置：Slot 6 型号：Toshiba 1TB SAS 硬盘 状态：Unconfigured(good)（未配置，状态良好） Foreign State：None（无外来配置） Other Error Count：9（有少量历史错误计数，不影响当前识别） 现状：硬盘物理连接正常且被 RAID 卡识别，但由于未被加入任何 RAID 组或创建为虚拟磁盘，RAID 卡不会将其呈现给操作系统 问题分析：为什么没有自动识别？\r原因一：Foreign State 为 None\r输出显示 Foreign State: None，说明 RAID 卡没有检测到这块盘上有可识别的旧 RAID 配置信息（或已被清除）。\n如果之前是 Linux 软 RAID（mdadm）：硬件 RAID 卡无法识别软 RAID 元数据，会将其视为空盘 如果之前是硬件 RAID：可能是 RAID 元数据不兼容或已被清除 原因二：需要手动创建虚拟磁盘\r在 Dell PERC 等 RAID 卡上，必须将物理磁盘配置为虚拟磁盘（Virtual Drive, VD），操作系统才能访问。\n解决方案：配置硬盘上线\r既然硬盘状态是 Unconfigured(good)，需要将其配置为单盘 RAID 0，操作系统才能识别。\n创建单盘 RAID 0\r执行以下命令将 Slot 6 的硬盘配置为虚拟磁盘：\n1 2 3 4 # 将 Slot 6 的硬盘配置为单盘 RAID 0 # [32:6] 代表 Enclosure 32, Slot 6 # -a0 代表第 0 个适配器 sudo /opt/MegaRAID/MegaCli/MegaCli64 -CfgLdAdd -r0 [32:6] -a0 预期结果\r命令执行成功后，终端会提示：\n1 Adapter 0: Created VD 3 再次运行 lsblk 命令，应该就能看到新增的 /dev/sdd 设备了。\n⚠️ 数据安全提示\r重要：-CfgLdAdd 命令会创建 RAID 结构并重写磁盘头部元数据，可能会影响原有数据的访问。\n不同场景的数据恢复方案\r原硬盘配置 Foreign State 数据恢复可能性 Linux 软 RAID (mdadm) None 配置上线后，可尝试使用 mdadm --assemble --scan 找回数据 硬件 RAID None RAID 卡已不识别旧配置，只能作为新盘挂载，能否恢复取决于分区表是否完整 全新硬盘 None 无数据丢失风险，可直接使用 刷新系统设备列表\r配置完成后，如果系统仍未自动识别新设备，可以手动刷新 SCSI 总线：\n1 2 3 4 # 扫描所有 SCSI 主机控制器，让系统重新识别硬盘 for host in /sys/class/scsi_host/host*/scan; do echo \u0026#34;- - -\u0026#34; \u0026gt; \u0026#34;$host\u0026#34; done 执行后使用 lsblk 或 fdisk -l 验证新硬盘是否已经可见。\n总结\r本次排查过程的关键步骤：\n✅ 安装 MegaCli 工具进行 RAID 卡管理 ✅ 使用 -PDList 命令查看所有物理硬盘状态 ✅ 定位问题：新硬盘处于 Unconfigured(good) 状态 ✅ 使用 -CfgLdAdd 命令创建单盘 RAID 0 ✅ 刷新系统设备列表，验证硬盘上线 通过这次排查，深刻理解了硬件 RAID 卡的工作机制：物理硬盘必须先配置为虚拟磁盘，操作系统才能访问。在日常运维中，遇到硬盘无法识别的问题时，应首先检查 RAID 卡层面的配置状态，而不是仅在操作系统层面排查。\n","date":"2025-12-30T00:00:00Z","image":"/p/raid_error/raid_error.png","permalink":"/p/raid_error/","title":"记一次RAID硬盘识别问题排查过程"},{"content":"在 Linux 上挂载 WebDAV\r本文档介绍如何在 Linux 系统上使用 davfs2 挂载 WebDAV 共享，以 Ubuntu 24.04 为例。\n安装 davfs2\r在终端中执行以下命令，安装 davfs2 包：\n1 2 sudo apt-get update sudo apt-get install davfs2 安装过程中可能会提示是否允许非 root 用户挂载 WebDAV，使用上下左右键可以切换到\u0026quot;是\u0026quot;选项。\n创建目录挂载点\r创建一个目录作为挂载点：\n1 sudo mkdir /mnt/webdav 配置 davfs2\r编辑 davfs2.conf 文件以配置 davfs2。打开配置文件：\n1 sudo nano /etc/davfs2/davfs2.conf 在文件中找到 use_locks 配置项，确保其值为 0。这样可以禁用文件锁定，因为有些 WebDAV 服务器不支持锁定。\n1 use_locks 0 保存并关闭文件（按 Ctrl+X，然后按 Y，最后按 Enter）。\n配置 davfs2 secrets 文件\r创建 secrets 文件以存储 WebDAV 服务器的用户名和密码。在终端中执行以下命令：\n1 sudo nano /etc/davfs2/secrets 添加类似以下的行，替换为你的 WebDAV 服务器的用户名和密码：\n1 http://your-webdav-url username password 将上述行中的内容替换为实际信息：\nhttp://your-webdav-url - 你的 WebDAV 服务器地址 username - 你的用户名 password - 你的密码 保存并关闭文件。\n设置文件权限\r为了确保 secrets 文件中的密码是安全的，设置文件权限：\n1 sudo chmod 600 /etc/davfs2/secrets 挂载 WebDAV 共享\r使用 mount 命令挂载 WebDAV 共享到之前创建的挂载点：\n1 sudo mount -t davfs http://your-webdav-url /mnt/webdav 开机自动挂载（可选）\r如果需要开机自动挂载，可以编辑 /etc/fstab 文件：\n1 sudo nano /etc/fstab 添加以下行：\n1 http://your-webdav-url /mnt/webdav davfs user,noauto 0 0 卸载 WebDAV 共享\r需要卸载时，执行：\n1 sudo umount /mnt/webdav ","date":"2025-11-11T00:00:00Z","image":"/p/webdav_on_linux/webdav_on_linux_zh-cn.png","permalink":"/p/webdav_on_linux/","title":"在 Linux 上挂载 WebDAV"},{"content":"NVIDIA GPU 驱动持久化配置与故障排查\r概述\r本文记录了一次由 GPU 驱动非持久化模式引发的监控异常问题，并介绍了 NVIDIA GPU 驱动持久化的原理与配置方法。\n问题现象\r在进行算法程序压测时，通过 Grafana 监控面板发现 Nvidia Exporter 服务运行不稳定，呈现时好时坏的状态：\n初步排查\r排除 Prometheus Scrape 问题\n在目标 GPU 服务器上手动执行 curl http://localhost:9835/metrics 命令，请求陷入超时状态，确认问题出在 Exporter 服务本身。\n调整日志级别\n将 Nvidia Exporter 的日志等级调整为 debug，但未发现明显的错误信息。\n定位根因\n手动执行 Nvidia Exporter 内部使用的查询命令：\n1 nvidia-smi --query-gpu=timestamp,driver_version,vgpu_driver_capability.heterogenous_multivGPU,count,name,serial,uuid,pci.bus_id,pci.domain,pci.bus,pci.device,pci.baseClass,pci.subClass,pci.device_id,pci.sub_device_id,vgpu_device_capability.fractional_multiVgpu,vgpu_device_capability.heterogeneous_timeSlice_profile,vgpu_device_capability.heterogeneous_timeSlice_sizes,vgpu_device_capability.homogeneous_placements,pcie.link.gen.current,pcie.link.gen.gpucurrent,pcie.link.gen.max,pcie.link.gen.gpumax,pcie.link.gen.hostmax,pcie.link.width.current,pcie.link.width.max,index,display_mode,display_active,persistence_mode,addressing_mode,accounting.mode,accounting.buffer_size,driver_model.current,driver_model.pending,vbios_version,inforom.img,inforom.oem,inforom.ecc,inforom.pwr,gpu_recovery_action,gom.current,gom.pending,fan.speed,pstate,clocks_event_reasons.supported,clocks_event_reasons.active,clocks_event_reasons.gpu_idle,clocks_event_reasons.applications_clocks_setting,clocks_event_reasons.sw_power_cap,clocks_event_reasons.hw_slowdown,clocks_event_reasons.hw_thermal_slowdown,clocks_event_reasons.hw_power_brake_slowdown,clocks_event_reasons.sw_thermal_slowdown,clocks_event_reasons.sync_boost,memory.total,memory.reserved,memory.used,memory.free,compute_mode,compute_cap,utilization.gpu,utilization.memory,utilization.encoder,utilization.decoder,utilization.jpeg,utilization.ofa,encoder.stats.sessionCount,encoder.stats.averageFps,encoder.stats.averageLatency,dramEncryption.mode.current,dramEncryption.mode.pending,ecc.mode.current,ecc.mode.pending,ecc.errors.corrected.volatile.device_memory,ecc.errors.corrected.volatile.dram,ecc.errors.corrected.volatile.register_file,ecc.errors.corrected.volatile.l1_cache,ecc.errors.corrected.volatile.l2_cache,ecc.errors.corrected.volatile.texture_memory,ecc.errors.corrected.volatile.cbu,ecc.errors.corrected.volatile.sram,ecc.errors.corrected.volatile.total,ecc.errors.corrected.aggregate.device_memory,ecc.errors.corrected.aggregate.dram,ecc.errors.corrected.aggregate.register_file,ecc.errors.corrected.aggregate.l1_cache,ecc.errors.corrected.aggregate.l2_cache,ecc.errors.corrected.aggregate.texture_memory,ecc.errors.corrected.aggregate.cbu,ecc.errors.corrected.aggregate.sram,ecc.errors.corrected.aggregate.total,ecc.errors.uncorrected.volatile.device_memory,ecc.errors.uncorrected.volatile.dram,ecc.errors.uncorrected.volatile.register_file,ecc.errors.uncorrected.volatile.l1_cache,ecc.errors.uncorrected.volatile.l2_cache,ecc.errors.uncorrected.volatile.texture_memory,ecc.errors.uncorrected.volatile.cbu,ecc.errors.uncorrected.volatile.sram,ecc.errors.uncorrected.volatile.total,ecc.errors.uncorrected.aggregate.device_memory,ecc.errors.uncorrected.aggregate.dram,ecc.errors.uncorrected.aggregate.register_file,ecc.errors.uncorrected.aggregate.l1_cache,ecc.errors.uncorrected.aggregate.l2_cache,ecc.errors.uncorrected.aggregate.texture_memory,ecc.errors.uncorrected.aggregate.cbu,ecc.errors.uncorrected.aggregate.sram,ecc.errors.uncorrected.aggregate.total,ecc.errors.uncorrected.volatile.sram.parity,ecc.errors.uncorrected.volatile.sram.secded,ecc.errors.uncorrected.aggregate.sram.parity,ecc.errors.uncorrected.aggregate.sram.secded,ecc.errors.uncorrected.aggregate.sram.thresholdExceeded,ecc.errors.uncorrected.aggregate.sram.l2,ecc.errors.uncorrected.aggregate.sram.sm,ecc.errors.uncorrected.aggregate.sram.mcu,ecc.errors.uncorrected.aggregate.sram.pcie,ecc.errors.uncorrected.aggregate.sram.other,retired_pages.single_bit_ecc.count,retired_pages.double_bit.count,retired_pages.pending,remapped_rows.correctable,remapped_rows.uncorrectable,remapped_rows.pending,remapped_rows.failure,remapped_rows.histogram.max,remapped_rows.histogram.high,remapped_rows.histogram.partial,remapped_rows.histogram.low,remapped_rows.histogram.none,temperature.gpu,temperature.gpu.tlimit,temperature.memory,power.management,power.draw,power.draw.average,power.draw.instant,power.limit,enforced.power.limit,power.default_limit,power.min_limit,power.max_limit,module.power.draw.average,module.power.draw.instant,module.power.limit,module.enforced.power.limit,module.power.default_limit,module.power.min_limit,module.power.max_limit,clocks.current.graphics,clocks.current.sm,clocks.current.memory,clocks.current.video,clocks.applications.graphics,clocks.applications.memory,clocks.default_applications.graphics,clocks.default_applications.memory,clocks.max.graphics,clocks.max.sm,clocks.max.memory,mig.mode.current,mig.mode.pending,gsp.mode.current,gsp.mode.default,c2c.mode,protected_memory.total,protected_memory.used,protected_memory.free,fabric.state,fabric.status,platform.chassis_serial_number,platform.slot_number,platform.tray_index,platform.host_id,platform.peer_type,platform.module_id,platform.gpu_fabric_guid --format=csv 关键发现：该命令执行时长在 3~10 秒之间波动，明显异常。测试环境中共有 8 块 GPU，其中 2 块正在被算法程序占用，其余 6 块处于空闲状态。\n回顾 NVIDIA 官方文档关于 GPU 驱动持久化的说明后，尝试启用持久化模式。\n解决方案\r临时启用持久化模式\r执行以下命令立即启用 GPU 驱动持久化：\n1 nvidia-smi -pm 1 再次执行上述查询命令，响应时间降至毫秒级，问题解决。\n配置开机自启动\r为确保系统重启后持久化配置生效，需要配置 systemd 服务：\n1. 创建服务配置文件\n1 sudo vim /usr/lib/systemd/system/nvidia-persistenced.service 2. 添加以下内容\n1 2 3 4 5 6 7 8 9 10 11 12 [Unit] Description=NVIDIA Persistence Daemon Wants=network.target [Service] Type=forking PIDFile=/var/run/nvidia-persistenced/nvidia-persistenced.pid ExecStart=/usr/bin/nvidia-persistenced --persistence-mode ExecStopPost=/bin/rm -rf /var/run/nvidia-persistenced [Install] WantedBy=multi-user.target 3. 启用并启动服务\n1 sudo systemctl enable nvidia-persistenced.service --now 验证效果\r配置完成后，监控系统恢复正常，GPU 使用情况采集稳定：\n技术原理\rGPU 驱动加载机制\rNVIDIA GPU 交互依赖内核模式驱动程序，该驱动程序的运行模式分为两种：\n持久化模式：驱动程序持续保持活跃状态 按需加载模式：驱动程序仅在有程序使用 GPU 时加载 驱动程序生命周期\r初始化阶段\n当首个程序尝试与 GPU 交互时，如果内核驱动未运行，系统会触发驱动加载并初始化 GPU 设备。\n去初始化阶段\n当所有 GPU 客户端程序退出后，驱动程序会执行 GPU 去初始化操作，实质上\u0026quot;关闭\u0026quot; GPU 设备。\n对用户的影响\r应用启动延迟\r首次触发 GPU 初始化时，由于需要执行 ECC 内存检查等操作，会产生 1~3 秒的延迟。若 GPU 已初始化，则无此延迟。\n驱动状态丢失\rGPU 去初始化后，非持久性状态信息(如功耗限制、时钟频率配置等)会丢失，下次初始化时恢复为默认值。启用持久化模式可避免此问题。\n平台差异\rWindows 平台\r在 Windows 系统中，内核驱动在系统启动时加载，并保持运行直至系统关闭。因此 Windows 用户通常无需关注驱动持久化问题。\n注意：驱动重载事件（如 TDR 触发或驱动更新）会导致非持久性状态重置。\nLinux 平台\rLinux 系统的行为取决于运行环境：\n图形界面环境\n若 X Server 运行在目标 GPU 上，内核驱动通常会从开机到关机持续活跃，由 X 进程维持连接。\n无头服务器环境\n在无图形界面的服务器（Headless Server）上，若缺少长期运行的 GPU 客户端，每次应用启动和停止都会触发驱动的加载与卸载。这在 高性能计算(HPC) 和 数据中心环境中极为常见，也是本次故障的根本原因。\n最佳实践建议\r生产环境强烈推荐启用 GPU 驱动持久化，特别是无头服务器场景 使用 systemd 服务确保持久化配置在系统重启后自动生效 监控系统应在启用持久化后进行充分测试，验证指标采集的稳定性 定期检查 nvidia-persistenced 服务状态，确保其正常运行 参考资料\rNVIDIA Driver Persistence 官方文档 ","date":"2025-11-07T00:00:00Z","image":"/p/nvidia_gpu_driver_persistence/nvidia_gpu_driver_persistence.png","permalink":"/p/nvidia_gpu_driver_persistence/","title":"NVIDIA GPU 驱动持久化"},{"content":"概述\rRedroid（Remote Android）是一个基于容器技术的 Android 运行时环境，可以在 Linux 服务器上运行完整的 Android 系统。本文将详细介绍如何从 AOSP 源码开始，构建一个功能完整的自定义 Redroid 镜像，并集成 GPS 定位、电池管理、Magisk Root 等高级功能。\n适用场景\r移动应用开发测试 自动化测试环境搭建 Android 逆向工程和安全研究 云端 Android 服务部署 前置条件\r在开始之前，请确保您的环境满足以下要求：\n硬件要求\rCPU: 推荐 8 核心以上，支持 x86_64 架构 内存: 至少 16GB RAM，推荐 32GB 存储: 至少 100GB 可用空间（SSD 推荐） 软件环境\r操作系统: Ubuntu 20.04/22.04 LTS 或其他现代 Linux 发行版 Docker: 版本 20.10 以上 Git: 版本 2.25 以上 Python 3: 用于运行构建脚本 网络要求\r稳定的网络连接（需要下载大量源码） 如遇网络问题，建议使用清华镜像源 步骤一：环境准备与源码下载\r创建工作目录\r1 2 3 # 创建专用的工作目录 cd /data mkdir redroid \u0026amp;\u0026amp; cd redroid 安装 Google Repo 工具\rRepo 是 Google 开发的用于管理多个 Git 仓库的工具，AOSP 项目必需。\n1 2 3 4 5 6 7 # 下载并安装 repo 工具 curl -k https://storage.googleapis.com/git-repo-downloads/repo \u0026gt; /usr/local/bin/repo chmod a+x /usr/local/bin/repo # 配置 Git 用户信息（必需） git config --global user.email \u0026#34;your-email@example.com\u0026#34; git config --global user.name \u0026#34;YourName\u0026#34; 注意: 请将邮箱和用户名替换为您的实际信息。\n初始化 AOSP 源码仓库\r1 2 3 4 5 6 7 # 初始化 repo，使用清华镜像源加速下载 repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest \\ --git-lfs --depth=1 -b android-15.0.0_r36 # 添加 Redroid 专用的本地 manifest 配置 git clone https://git.coderkang.top/Android/local_manifests.git \\ .repo/local_manifests -b 15.0.0 同步源码\r1 2 # 开始同步源码（耗时较长，请耐心等待） repo sync -c 提示: 首次同步可能需要 2-4 小时，具体时间取决于网络状况。建议使用 screen 或 tmux 在后台运行。\n步骤二：Magisk 模块集成\rMagisk 是流行的 Android Root 解决方案，提供系统级权限管理。\n提取 Magisk 组件\r1 2 3 # 进入 Magisk 目录并执行提取脚本 cd /data/redroid/vendor/magisk python3 magisk.py 该脚本会自动下载最新版本的 Magisk APK 并提取必要的二进制文件和模块，集成到系统镜像中。\n步骤三：应用定制补丁\r下载并应用 Redroid 补丁\r1 2 3 4 5 6 7 8 # 回到主目录 cd /data # 获取 Redroid 专用补丁集 git clone https://git.coderkang.top/Android/redroid-patches.git # 应用所有补丁到源码 ./redroid-patches/apply-patch.sh /data/redroid 这些补丁包含：\nGPS 功能: 启用定位服务支持 电池管理: 模拟电池状态和电源管理 网络增强: 改进网络连接稳定性 性能优化: 针对容器环境的性能调优 步骤四：Docker 构建环境搭建\r创建构建容器\r1 2 3 4 5 6 7 8 9 10 # 获取构建工具 git clone https://git.coderkang.top/Android/redroid-doc.git cd redroid-doc/android-builder-docker # 构建 Docker 镜像（包含所有构建依赖） docker build \\ --build-arg userid=$(id -u) \\ --build-arg groupid=$(id -g) \\ --build-arg username=$(id -un) \\ -t redroid-builder . 启动构建环境\r1 2 3 4 5 6 # 启动构建容器，挂载源码目录 docker run -it --rm \\ --hostname redroid-builder \\ --name redroid-builder \\ -v /data/redroid:/src \\ redroid-builder 步骤五：编译 Android 系统\r配置构建环境\r1 2 3 4 5 6 7 8 # 在容器内执行以下命令 cd /src # 初始化构建环境 . build/envsetup.sh # 选择构建目标（ARM64 架构，用户调试版本） lunch redroid_arm64_only-ap3a-userdebug 开始编译\r1 2 # 启动编译过程（时间较长，建议使用 -j 参数指定并行度） m -j$(nproc) 编译时间: 根据硬件配置，编译时间通常在 1-3 小时之间。\n步骤六：镜像打包与部署\r创建 Docker 镜像\r编译完成后，需要将生成的系统文件打包为 Docker 镜像。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 退出构建容器，回到宿主机 exit # 进入编译输出目录 cd /data/redroid/out/target/product/redroid_arm64_only # 挂载系统镜像（只读） sudo mount system.img system -o ro sudo mount vendor.img vendor -o ro # 打包为 Docker 镜像 sudo tar --xattrs -c vendor -C system --exclude=\u0026#34;./vendor\u0026#34; . | \\ docker import -c \u0026#39;ENTRYPOINT [\u0026#34;/init\u0026#34;, \u0026#34;androidboot.hardware=redroid\u0026#34;, \u0026#34;ro.setupwizard.mode=DISABLED\u0026#34;]\u0026#39; \\ - redroid:custom # 卸载镜像文件 sudo umount system vendor 验证镜像\r1 2 3 4 5 6 7 8 # 查看创建的镜像 docker images | grep redroid # 测试启动容器 docker run -itd --rm --memory-swappiness=0 \\ --name redroid-test \\ -p 5555:5555 \\ redroid:custom 功能验证与使用\r验证 Root 权限\r1 2 3 4 5 # 连接到 Redroid 容器 adb connect localhost:5555 # 验证 Root 权限 adb shell su -c \u0026#34;id\u0026#34; 验证 GPS 功能\r1 2 # 检查 GPS 服务状态 adb shell dumpsys location 验证电池管理\r1 2 # 查看电池状态 adb shell dumpsys battery 常见问题与解决方案\r编译错误\r问题: 内存不足导致编译失败\n1 2 # 解决方案：减少并行度 m -j4 # 使用较少的并行任务 问题: 磁盘空间不足\n1 2 # 解决方案：清理编译缓存 make clean 运行时问题\r问题: 容器无法启动\n1 2 3 4 5 6 7 # 检查内核模块 lsmod | grep binder lsmod | grep ashmem # 如果缺失，加载模块 sudo modprobe binder_linux sudo modprobe ashmem_linux 问题: ADB 连接失败\n1 2 3 # 重启 ADB 服务 adb kill-server adb start-server 优化建议\r性能优化\r内存配置: 为容器分配足够的内存（推荐 4GB+） CPU 配置: 启用 CPU 热插拔支持 存储优化: 使用 SSD 存储提升 I/O 性能 安全考虑\r网络隔离: 使用自定义 Docker 网络 权限控制: 限制容器权限，避免特权模式 数据持久化: 合理配置数据卷挂载 总结\r通过本文的详细指导，您已经成功构建了一个功能完整的自定义 Redroid 镜像。这个镜像集成了 GPS、电池管理、Magisk Root 等高级功能，可以满足各种开发和测试需求。\n在实际使用中，您可以根据具体需求进一步定制系统配置，添加更多功能模块，或者优化性能参数。Redroid 的灵活性使其成为 Android 开发和测试的理想选择。\n下一步\r探索更多 Magisk 模块集成 配置持续集成/持续部署（CI/CD） 搭建多实例集群环境 集成自动化测试框架 ","date":"2025-09-17T00:00:00Z","image":"/p/custom_redroid/banner.png","permalink":"/p/custom_redroid/","title":"自定义 Redroid 镜像：从源码构建到功能增强"},{"content":"前言\r在此前的文章中，介绍了如何在 Docker 中配置 Redroid 并通过 Magisk 获取 Root 权限。随着部分开源模块对 Magisk 版本的要求提升，本文采用 ayasa520/redroid-script 进行镜像构建与 Root 方案更新，同时引入 AutoJS 以满足自启动与自动化点击等需求，记录从构建、编排到排障的完整流程。\n合规与风险提示\n本文仅用于学习与研究。涉及证书、Root、系统模块与网络重定向的操作可能触发合规与安全风险。任何生产或对第三方系统的操作需确保具备合法授权。文中出现的设备标识（如 IMEI）为示例值，请替换为自有、合规的虚构数据。\n环境与准备\rDocker / Docker Compose 可运行 Redroid 的 Linux 主机 ADB / Scrcpy（用于调试与投屏） 基础应用：MT 管理器、Termux、AutoJsPro、JustTrustMe Magisk 模块：LSPosed、AlwaysTrustUserCerts、Systemless Hosts 统一资源包（文末提供下载地址） 构建内置 Magisk 的 Redroid 镜像\r获取脚本并安装依赖\r1 2 3 4 5 git clone https://github.com/ayasa520/redroid-script cd redroid-script apt update \u0026amp;\u0026amp; apt install -y lzip pip install -r requirements.txt 扩展 Android 版本枚举（示例：14/15）\r如需 Android 14/15，请在 redroid.py 中补全可选版本：\n1 2 3 4 5 6 7 8 9 10 def main(): ... parser.add_argument(\u0026#39;-a\u0026#39;, \u0026#39;--android-version\u0026#39;, dest=\u0026#39;android\u0026#39;, help=\u0026#39;Specify the Android version to build\u0026#39;, default=\u0026#39;11.0.0\u0026#39;, - choices=[\u0026#39;13.0.0\u0026#39;, \u0026#39;12.0.0\u0026#39;, \u0026#39;12.0.0_64only\u0026#39;, \u0026#39;11.0.0\u0026#39;, \u0026#39;10.0.0\u0026#39;, \u0026#39;9.0.0\u0026#39;, \u0026#39;8.1.0\u0026#39;]) + choices=[\u0026#39;15.0.0_64only\u0026#39;, \u0026#39;15.0.0\u0026#39;, \u0026#39;14.0.0_64only\u0026#39;, \u0026#39;14.0.0\u0026#39;, + \u0026#39;13.0.0_64only\u0026#39;, \u0026#39;13.0.0\u0026#39;, \u0026#39;12.0.0\u0026#39;, \u0026#39;12.0.0_64only\u0026#39;, + \u0026#39;11.0.0\u0026#39;, \u0026#39;10.0.0\u0026#39;, \u0026#39;9.0.0\u0026#39;, \u0026#39;8.1.0\u0026#39;]) 构建内置 Magisk 的镜像\r1 2 python3 redroid.py -a 15.0.0_64only -m # 新镜像：redroid/redroid:15.0.0_64only_magisk 启动 Redroid\rdocker-compose.yaml 示例：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 name: android services: redroid_15_1: image: redroid/redroid:15.0.0_64only_magisk container_name: redroid_15_1 restart: unless-stopped privileged: true networks: android: ipv4_address: 172.18.0.252 ports: - \u0026#34;45555:5555\u0026#34; volumes: - ./redroid_15_1:/data command: - androidboot.hardware=mt6891 - androidboot.hwc=CN - androidboot.redroid_height=2400 - androidboot.redroid_width=1080 - ro.boot.hwc=CN - ro.product.manufacturer=Xiaomi - ro.product.brand=Xiaomi - ro.product.model=2211133C - ro.product.marketname=Xiaomi 13 - ro.product.device=fuxi - ro.product.name=fuxi - ro.build.product=fuxi - ro.product.mod_device=fuxi - ro.secure=0 - ro.product.locale=zh-CN - ro.product.locale.language=zh - ro.product.locale.region=CN - persist.sys.locale=zh-CN - persist.sys.locale_list=zh-CN,en-US - persist.sys.timezone=Asia/Shanghai - persist.sys.time_12_24=24 networks: android: driver: bridge ipam: config: - subnet: 172.18.0.0/16 启动与连接：\n1 2 3 4 5 6 7 docker compose up -d docker compose ps docker compose logs -f redroid_15_1 adb connect 127.0.0.1:45555 adb devices adb shell whoami 运行正常后可通过 45555 端口访问 ADB 服务。 基础软件与模块安装\r软件（统一资源包，见文末）\nMT 管理器 Termux AutoJsPro JustTrustMe Magisk 模块\nLSPosed AlwaysTrustUserCerts Systemless Hosts 建议流程：先安装用户证书（见下文），再启用 AlwaysTrustUserCerts，随后按需安装/启用其他模块，以提升 Zygisk 生效的稳定性。\nRoot 授权与 su 路径\r现象\n在 Termux 中执行 su 可正常触发 Magisk 授权弹窗； 在 MT 管理器、Shizuku 中无法获取 Root； logcat 无明显异常信息。 处置\n显式指定 su 路径为 /sbin/su； 参考项目 issue：https://github.com/ayasa520/redroid-script/issues/47#issuecomment-3242690759。 说明 不同 App 的 Root 发现与调用路径存在差异，显式路径可避免环境变量或挂载策略导致的可见性问题。若自定义了 PATH、SELinux 或 overlayfs，也需同步评估其影响。\nZygisk 不生效的排查思路\r症状 Magisk 中 Zygisk 始终提示“需要重启”，重启后依旧未生效，日志信息有限。\n可复用操作\n在系统设置中先安装用户证书； 通过 LSPosed/模块管理启用 AlwaysTrustUserCerts； 重启 Redroid，使 Zygisk 与相关模块完成初始化与挂载。 如仍不生效，可尝试清理模块缓存、核对 LSPosed 版本兼容性，并在必要时回退至稳定的 Magisk/Redroid 组合版本。\n部署 AutoJS 自动化\r适用场景 需要在特定 App 启动后执行自动点击、心跳或表单填充等动作。\n合规说明 以下方法仅限自有环境与授权测试场景，请勿用于未授权的应用或服务。\n准备 AutoJS 服务\r1 2 3 4 5 6 7 # 解压资源包中的 autojserver.tar.gz 至编排目录 tar -zxvf autojserver.tar.gz cd autojserver chmod +x main.sh bash main.sh # 初始化后将生成自签证书等运行所需文件 Compose 编排\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 name: android services: redroid_15_1: image: redroid/redroid:15.0.0_64only_magisk container_name: redroid_15_1 restart: unless-stopped privileged: true networks: android: ipv4_address: 172.18.0.252 ports: - \u0026#34;45555:5555\u0026#34; volumes: - ./redroid_15_1:/data command: - androidboot.hardware=mt6891 - androidboot.hwc=CN - androidboot.redroid_height=2400 - androidboot.redroid_width=1080 - ro.boot.hwc=CN - ro.product.manufacturer=Xiaomi - ro.product.brand=Xiaomi - ro.product.model=2211133C - ro.product.marketname=Xiaomi 13 - ro.product.device=fuxi - ro.product.name=fuxi - ro.build.product=fuxi - ro.product.mod_device=fuxi - ro.secure=0 - ro.product.locale=zh-CN - ro.product.locale.language=zh - ro.product.locale.region=CN - persist.sys.locale=zh-CN - persist.sys.locale_list=zh-CN,en-US - persist.sys.timezone=Asia/Shanghai - persist.sys.time_12_24=24 autojserver: image: autojserver:latest build: context: ./autojserver dockerfile: Dockerfile container_name: autojserver restart: unless-stopped working_dir: /data networks: android: ipv4_address: 172.18.0.251 command: [\u0026#34;python\u0026#34;, \u0026#34;/data/main.py\u0026#34;] volumes: - ./autojserver:/data networks: android: driver: bridge ipam: config: - subnet: 172.18.0.0/16 启动与日志查看\r1 2 3 docker compose up -d docker compose ps docker compose logs -f autojserver 证书与 hosts 注入\r1 2 3 4 5 # 用户证书：用于系统设置中手动安装 cp ./autojserver/ca.crt ./redroid_15_1/media/0/Download/ # Systemless Hosts：需确保模块已安装 cp -rf ./autojserver/hosts ./redroid_15_1/adb/modules/hosts/system/etc/ 随后在 系统设置 → 安全 → 加密与凭据（或证书管理）→ 从存储安装 中安装 ca.crt 用户证书，并在 JustTrustMe 作用域中勾选 AutoJS/目标 App。完成后重启 Redroid。\n资源下载\r统一资源包\n软件：MT 管理器、Termux、AutoJsPro、JustTrustMe 模块：LSPosed、AlwaysTrustUserCerts、Systemless Hosts ","date":"2025-09-02T00:00:00Z","image":"/p/autojs_on_redroid/banner.png","permalink":"/p/autojs_on_redroid/","title":"Autojs 在 Redroid 上使用"},{"content":"基于 PaddlePaddle 在昇腾 310P 上的部署实践，使用 PyInstaller 打包 PaddleOCR。\n适用范围与前置条件\r目标：为 Ascend 310P NPU 运行环境打包一个基于 PaddleOCR 的命令行程序。 系统/架构：Linux x86_64（示例路径基于 Conda + Python 3.11）。 关键版本： Python：3.11（如不同请调整路径） PyInstaller：6.x 或更高 PaddlePaddle（Ascend 版本） 与 310P 运行时已正确安装 环境自检： 1 2 3 python -V pyinstaller --version python -c \u0026#34;import paddle, cv2; print(paddle.__version__, cv2.__version__)\u0026#34; Spec 文件\r通过以下 test_ocr.spec 文件进行打包：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 # -*- mode: python ; coding: utf-8 -*- from PyInstaller.utils.hooks import copy_metadata, collect_data_files datas = [(\u0026#39;/opt/PaddleX/paddlex\u0026#39;, \u0026#39;paddlex\u0026#39;)] # PaddleX 项目源代码 Python 包所在目录 datas += [(\u0026#39;/root/miniconda3/envs/asr_ocr_npu/lib/python3.11/site-packages/paddle\u0026#39;, \u0026#39;paddle\u0026#39;)] # Paddle PyPI 包所在目录 datas += collect_data_files(\u0026#39;lmdb\u0026#39;) datas += copy_metadata(\u0026#39;ftfy\u0026#39;) datas += copy_metadata(\u0026#39;lxml\u0026#39;) datas += copy_metadata(\u0026#39;opencv-contrib-python\u0026#39;) datas += copy_metadata(\u0026#39;pyclipper\u0026#39;) datas += copy_metadata(\u0026#39;pypdfium2\u0026#39;) datas += copy_metadata(\u0026#39;ultra-infer-npu-python\u0026#39;) datas += copy_metadata(\u0026#39;scikit-learn\u0026#39;) a = Analysis( [\u0026#39;./test_ocr.py\u0026#39;], # 需要打包的入口文件 pathex=[], binaries=[(\u0026#39;/root/miniconda3/envs/asr_ocr_npu/lib/python3.11/site-packages/paddle/libs\u0026#39;, \u0026#39;.\u0026#39;)], # Paddle 动态库所在目录 datas=datas, hiddenimports=[\u0026#39;cv2\u0026#39;, \u0026#39;pypdfium2\u0026#39;, \u0026#39;ultra_infer\u0026#39;, \u0026#39;paddle.cinn_config\u0026#39;], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], noarchive=False, optimize=0, ) pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name=\u0026#39;test_ocr\u0026#39;, debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=True, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, ) coll = COLLECT( exe, a.binaries, a.datas, strip=False, upx=True, upx_exclude=[], name=\u0026#39;test_ocr\u0026#39;, ) 构建\r1 pyinstaller ./test_ocr.spec 各段含义说明\rdatas：打包运行期需要的包与数据文件（如 ftfy、lxml、opencv-contrib-python 的元数据/资源）。 binaries：打入 paddle/libs 下的共享库，以便目标机无需预装 Paddle 也可运行。 hiddenimports：显式声明 PyInstaller 可能漏掉的依赖（如 cv2、paddle.cinn_config、ultra_infer）。 UPX：用于压缩体积；若出现启动异常或被误报，尝试设置 upx=False。 单文件 vs 单目录：此方案构建为单目录（COLLECT），对大型框架更稳妥。 ","date":"2025-08-10T00:00:00Z","image":"/p/pyinstaller_paddleocr_ascend310p/pyinstaller_paddleocr_ascend310P.png","permalink":"/p/pyinstaller_paddleocr_ascend310p/","title":"在 Ascend 310P 环境中使用 PyInstaller 打包 PaddleOCR"},{"content":"简介\rSMB（Server Message Block）是一种网络文件共享协议，广泛用于在不同操作系统之间共享文件和打印机。在 Ubuntu 22.04 上安装和配置 SMB 服务可以方便地在 Linux 和 Windows 系统之间共享文件。本文将详细介绍如何在 Ubuntu 22.04 上安装、配置和使用 SMB 服务。\n安装 SMB 服务\r更新系统包\r首先，确保系统包是最新的：\n1 2 sudo apt update sudo apt upgrade -y 安装 Samba\rSamba 是 Linux 系统上实现 SMB 协议的开源软件包：\n1 sudo apt install samba samba-common-bin -y 验证安装\r检查 Samba 服务状态：\n1 2 sudo systemctl status smbd sudo systemctl status nmbd 配置 SMB 服务\r备份默认配置文件\r在修改配置文件之前，先备份原始文件：\n1 sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.backup 编辑配置文件\r使用文本编辑器编辑 SMB 配置文件：\n1 sudo nano /etc/samba/smb.conf 基本配置示例\r在配置文件中添加以下内容：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [global] workgroup = WORKGROUP server string = Ubuntu SMB Server log file = /var/log/samba/log.%m max log size = 1000 logging = file panic action = /usr/share/samba/panic-action %d server role = standalone server obey pam restrictions = yes unix password sync = yes passwd program = /usr/bin/passwd %u passwd chat = *Enter\\snew\\s*\\spassword:* %n\\n *Retype\\snew\\s*\\spassword:* %n\\n *password\\supdated\\ssuccessfully* . pam password change = yes map to guest = bad password usershare allow guests = yes [shared] comment = 共享文件夹 path = /srv/samba/shared browseable = yes read only = no guest ok = yes create mask = 0755 directory mask = 0755 创建共享目录\r创建用于共享的目录：\n1 2 3 sudo mkdir -p /srv/samba/shared sudo chown nobody:nogroup /srv/samba/shared sudo chmod 777 /srv/samba/shared 验证配置文件\r使用 testparm 命令验证配置文件语法：\n1 sudo testparm 用户管理\r创建 SMB 用户\r如果需要用户认证，首先创建系统用户：\n1 sudo adduser smbuser 然后将用户添加到 SMB 数据库：\n1 sudo smbpasswd -a smbuser 启用/禁用用户\r启用 SMB 用户：\n1 sudo smbpasswd -e smbuser 禁用 SMB 用户：\n1 sudo smbpasswd -d smbuser 删除用户\r删除 SMB 用户：\n1 sudo smbpasswd -x smbuser 启动和管理服务\r启动服务\r启动 SMB 服务：\n1 2 sudo systemctl start smbd sudo systemctl start nmbd 设置开机自启\r设置服务开机自启动：\n1 2 sudo systemctl enable smbd sudo systemctl enable nmbd 重启服务\r重启 SMB 服务：\n1 2 sudo systemctl restart smbd sudo systemctl restart nmbd 检查服务状态\r检查服务运行状态：\n1 2 sudo systemctl status smbd sudo systemctl status nmbd 防火墙配置\r如果启用了防火墙，需要开放 SMB 相关端口：\n1 sudo ufw allow \u0026#39;Samba\u0026#39; 或者手动开放端口：\n1 2 3 4 sudo ufw allow 139/tcp sudo ufw allow 445/tcp sudo ufw allow 137/udp sudo ufw allow 138/udp 测试连接\r本地测试\r在本地测试 SMB 共享：\n1 smbclient -L localhost 挂载共享\r在 Linux 客户端挂载 SMB 共享：\n1 2 sudo mkdir /mnt/smbshare sudo mount -t cifs //SERVER_IP/shared /mnt/smbshare -o username=smbuser Windows 客户端\r在 Windows 资源管理器地址栏输入：\n1 \\\\SERVER_IP\\shared 高级配置\r家目录共享\r为每个用户配置家目录共享：\n1 2 3 4 5 6 7 [homes] comment = 用户家目录 browseable = no read only = no create mask = 0700 directory mask = 0700 valid users = %S 只读共享\r配置只读共享：\n1 2 3 4 5 6 [readonly] comment = 只读共享 path = /srv/samba/readonly browseable = yes read only = yes guest ok = yes 安全设置\r增强安全性的配置：\n1 2 3 4 5 6 7 8 9 10 [global] # 禁用 SMB1 协议 server min protocol = SMB2 # 启用加密 server signing = mandatory # 限制访问IP hosts allow = 192.168.1.0/24 127.0.0.1 hosts deny = ALL 常见问题\r权限问题\r如果遇到权限问题，检查以下设置：\n文件系统权限 SELinux 设置（如果启用） SMB 用户权限 连接问题\r如果无法连接，检查：\n防火墙设置 网络连通性 服务状态 性能优化\r优化 SMB 性能的配置：\n1 2 3 4 5 6 7 [global] socket options = TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF=131072 SO_SNDBUF=131072 read raw = yes write raw = yes max xmit = 65535 dead time = 15 getwd cache = yes 监控和日志\r查看日志\rSMB 日志文件位置：\n1 2 sudo tail -f /var/log/samba/log.smbd sudo tail -f /var/log/samba/log.nmbd 连接状态\r查看当前连接：\n1 sudo smbstatus 共享列表\r列出所有共享：\n1 sudo smbclient -L localhost 总结\r通过本文的介绍，可以成功在 Ubuntu 22.04 上安装和配置 SMB 服务。SMB 服务为跨平台文件共享提供了便利的解决方案，适用于家庭网络和企业环境。\n关键要点：\n定期备份配置文件 合理设置用户权限 配置适当的防火墙规则 监控服务状态和日志 根据需要调整性能参数 正确配置的 SMB 服务将为网络环境提供稳定可靠的文件共享功能。\n","date":"2025-07-04T00:00:00Z","image":"/p/ubuntu_smb/ubuntu_smb.png","permalink":"/p/ubuntu_smb/","title":"Ubuntu 22.04 安装 SMB 服务"},{"content":"概述\r本文详细介绍了在昇腾 310P 平台上部署 PaddlePaddle 模型的多种方案，包括各方案的优缺点、实施步骤以及常见问题的解决方法。主要适用于 ASR（自动语音识别）和 OCR（光学字符识别）等应用场景。\n方案概览\r方案 描述 状态 推荐度 方案一 使用 paddle-custom-npu pip 包 ❌ 存在依赖问题 ⭐ 方案二 编译 PaddleCustomDevice ⚠️ 精度问题待解决 ⭐⭐ 方案三 paddle2onnx + onnxruntime_cann ⚠️ 推理速度慢 ⭐⭐⭐ 方案四 paddle2onnx + OM 模型 ⚠️ 动态 Shape 问题 ⭐⭐⭐ 方案五 PaddleX 高性能推理 ✅ 推荐使用 ⭐⭐⭐⭐⭐ 方案一：pip 包安装方案\r概述\r采用官方提供的 paddle-custom-npu pip 包进行快速部署，但经测试存在运行时依赖库缺失问题。\n部署步骤\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 安装基础依赖 pip install psutil attrs decorator # 安装 Python 包依赖 pip3 install py3Fdfs imageio pyheif whatimage shapely pyclipper minio \\ scikit-image imgaug lmdb pykafka gunicorn Pillow==9.5.0 # 安装 PaddlePaddle 和 NPU 支持 pip install paddlepaddle -i https://www.paddlepaddle.org.cn/packages/nightly/cpu/ pip install paddle-custom-npu -i https://www.paddlepaddle.org.cn/packages/nightly/npu/ pip install paddleocr # 修复版本兼容性问题 pip install protobuf==3.20.0 配置修改\r1 2 3 4 5 # 修改 PaddleSpeech 执行器配置 vim /root/miniconda3/envs/asr_ocr/lib/python3.9/site-packages/paddlespeech/cli/executor.py +92 # 修改 Paddle 核心模块配置 vim /root/miniconda3/envs/asr_ocr/lib/python3.9/site-packages/paddle/fluid/core.py +386 存在问题\r缺少运行时依赖库，无法正常进行推理预测 补充相关依赖库后仍报内部错误 方案二：编译安装方案\r概述\r通过编译 PaddleCustomDevice 源码进行安装，但存在精度支持问题。\n参考资料\r华为昇腾 NPU-PaddlePaddle 深度学习平台 PaddleCustomDevice NPU 后端 环境准备\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 系统依赖安装 apt-get update -y \u0026amp;\u0026amp; apt-get install -y \\ zlib1g zlib1g-dev libsqlite3-dev openssl libssl-dev libffi-dev \\ libbz2-dev libxslt1-dev unzip pciutils net-tools libblas-dev \\ gfortran libblas3 liblapack-dev liblapack3 libopenblas-dev git # Python 依赖安装 pip install psutil attrs decorator pyyaml pathlib2 scipy requests \\ psutil absl-py sympy numpy==1.25.0 scipy # CANN 工具链安装 ./Ascend-cann-kernels-310p_8.0.RC2_linux.run --install ./Ascend-cann-nnal_8.0.RC2_linux-aarch64.run --install # 算子包安装 wget -q https://paddle-ascend.bj.bcebos.com/code-share-master.zip --no-check-certificate . /usr/local/Ascend/ascend-toolkit/set_env.sh unzip code-share-master.zip cd code-share-master/build \u0026amp;\u0026amp; bash build_ops.sh chmod +x aie_ops.run \u0026amp;\u0026amp; ./aie_ops.run --extract=/usr/local/Ascend/ 环境变量配置\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 日志级别配置 (0:debug 1:info 2:warning 3:error 4:null) export ASCEND_GLOBAL_LOG_LEVEL=3 # HCCL 配置 export HCCL_CONNECT_TIMEOUT=7200 export HCCL_WHITELIST_DISABLE=1 export HCCL_SECURITY_MODE=1 export HCCL_BUFFSIZE=120 # PaddlePaddle NPU 配置 export FLAGS_npu_storage_format=0 export FLAGS_use_stride_kernel=0 export FLAGS_allocator_strategy=naive_best_fit export PADDLE_XCCL_BACKEND=npu 编译安装\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 进入 NPU 后端目录 cd PaddleCustomDevice/backends/npu # 安装 PaddlePaddle CPU 版本 pip install paddlepaddle -i https://www.paddlepaddle.org.cn/packages/nightly/cpu/ # 配置编译选项 export WITH_TESTING=OFF # 执行编译 bash tools/compile.sh # 安装编译产物 pip install build/dist/paddle_custom_npu*.whl 功能验证\r1 2 3 4 5 6 7 8 9 # 检查可用硬件后端 python -c \u0026#34;import paddle; print(paddle.device.get_all_custom_device_type())\u0026#34; # 预期输出: [\u0026#39;npu\u0026#39;] # 检查版本信息 python -c \u0026#34;import paddle_custom_device; paddle_custom_device.npu.version()\u0026#34; # PaddlePaddle 健康检查 python -c \u0026#34;import paddle; paddle.utils.run_check()\u0026#34; 存在问题\r报错：The soc version does not support bf16 / fp32 for calculations, please change the setting of cubeMathType or the Dtype of input tensor.\n方案三：ONNX Runtime CANN 方案\r概述\r使用 paddle2onnx 进行模型转换，配合 onnxruntime_cann 的 CANNExecutionProvider 进行推理。\n依赖安装\r1 2 3 4 5 6 7 8 9 10 11 # 基础依赖 pip install psutil attrs decorator pyyaml pathlib2 scipy requests \\ psutil absl-py sympy numpy==1.25.0 scipy packaging # 图像处理依赖 pip install opencv-python Pillow==9.5.0 # 项目依赖 pip install flask py3Fdfs imageio pyheif whatimage shapely pyclipper \\ minio scikit-image imgaug lmdb pykafka gunicorn protobuf==3.20.0 \\ Pyinstaller nacos-python-sdk filetype 模型转换\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # OCR 识别模型转换 paddle2onnx --model_dir ./ch_PP-OCRv4_rec_infer \\ --model_filename inference.pdmodel \\ --params_filename inference.pdiparams \\ --save_file ./ch_PP-OCRv4_rec_infer.onnx \\ --opset_version 11 \\ --enable_onnx_checker True # OCR 检测模型转换 paddle2onnx --model_dir ./ch_PP-OCRv4_det_infer \\ --model_filename inference.pdmodel \\ --params_filename inference.pdiparams \\ --save_file ./ch_PP-OCRv4_det_infer.onnx \\ --opset_version 11 \\ --enable_onnx_checker True # 文本方向分类模型转换 paddle2onnx --model_dir ./ch_ppocr_mobile_v2.0_cls_infer \\ --model_filename inference.pdmodel \\ --params_filename inference.pdiparams \\ --save_file ./ch_ppocr_mobile_v2.0_cls.onnx \\ --opset_version 11 \\ --enable_onnx_checker True 性能优化\rNPU 推理性能优化分析：\n性能问题原因：\nJIT 编译延迟：NPU 使用 aclop JIT 算子库，首次运行需要算子编译和缓存 算子回退：部分算子不支持会回退到 CPU，导致频繁的内存拷贝 优化方案：\n1 2 3 # 禁用 JIT 编译，使用预编译算子 export FLAGS_npu_jit_compile=0 export FLAGS_use_stride_kernel=0 Warm-up 预热： 推理前进行 5-10 次预热，然后执行正式推理测试。\n算子缓存机制： 执行后会生成 kernel_meta 目录，包含算子缓存文件，提升后续执行性能。\n存在问题\r推理速度相比 CPU 没有明显提升 需要手动进行 warm-up 操作 kernel_meta 缓存文件可能占用较大磁盘空间 方案四：OM 模型部署方案\r概述\r将 ONNX 模型转换为昇腾专用的 OM 模型格式，使用 ACL 接口进行推理。\n模型转换流程\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Step 1: Paddle 模型转 ONNX paddle2onnx --model_dir ./ch_PP-OCRv4_rec_infer \\ --model_filename inference.pdmodel \\ --params_filename inference.pdiparams \\ --save_file ./ch_PP-OCRv4_rec_infer.onnx \\ --opset_version 11 \\ --enable_onnx_checker True # Step 2: ONNX 模型转 OM atc --model=./rec/ch_PP-OCRv4_rec_infer.onnx \\ --framework=5 \\ --input_format=NCHW \\ --output=./rec/ch_PP-OCRv4_rec_infer.om \\ --soc_version=Ascend310P3 \\ --input_shape=\u0026#34;x:1,3,48,320\u0026#34; 技术难点\r动态 Shape 问题：\n1 2 3 4 5 # 模型信息示例 Input name : x Input shape : [\u0026#39;DynamicDimension.0\u0026#39;, 3, \u0026#39;DynamicDimension.1\u0026#39;, \u0026#39;DynamicDimension.2\u0026#39;] Output name : sigmoid_0.tmp_0 Output shape: [\u0026#39;DynamicDimension.3\u0026#39;, 1, \u0026#39;DynamicDimension.4\u0026#39;, \u0026#39;DynamicDimension.5\u0026#39;] 内存分配问题： get_output_size_by_index 返回 0，导致 acl.rt.malloc 分配内存失败。\n解决方案\r系统自动分配：创建空的 aclDataBuffer，系统内部自动分配内存 用户预估分配：根据最大可能输出预先分配内存 存在问题\r动态 Shape 支持不完善 内存管理复杂 调试困难 方案五：PaddleX 高性能推理（推荐）\r概述\r使用 PaddleX 提供的高性能推理插件，支持固定 Shape 的 OM 模型推理，性能优秀且稳定可靠。\n安装配置\r1 2 3 4 5 6 7 # 安装 PaddleX git clone https://github.com/PaddlePaddle/PaddleX.git cd PaddleX pip install -e \u0026#34;.[base]\u0026#34; # 安装高性能推理插件 paddlex --install hpi-npu 手动编译（可选）\r1 2 3 4 5 6 7 8 9 10 11 12 cd PaddleX/libs/ultra-infer/python unset http_proxy https_proxy # 配置编译选项 export ENABLE_OM_BACKEND=ON ENABLE_ORT_BACKEND=ON export ENABLE_PADDLE_BACKEND=OFF WITH_GPU=OFF DEVICE_TYPE=NPU export NPU_HOST_LIB=/usr/local/Ascend/ascend-toolkit/latest/aarch64-linux/lib64 # 编译安装 python setup.py build python setup.py bdist_wheel pip install dist/ultra_infer_npu*.whl 模型转换\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 paddlex --install paddle2onnx # 使用 PaddleX 转换为 ONNX paddlex --paddle2onnx \\ --opset_version 11 \\ --paddle_model_dir \u0026lt;PaddlePaddle模型目录\u0026gt; \\ --onnx_model_dir \u0026lt;ONNX模型目录\u0026gt; # 转换为 OM 模型 atc --model=inference.onnx \\ --framework=5 \\ --output=inference \\ --soc_version=Ascend310P3 \\ --input_shape \u0026#34;x:1,3,48,320\u0026#34; # FP32 精度转换 atc --model=inference.onnx \\ --framework=5 \\ --output=inference \\ --soc_version=Ascend310P3 \\ --input_shape \u0026#34;x:1,3,48,320\u0026#34; \\ --precision_mode_v2=origin 推理代码示例\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 # -*- encoding=utf-8 -*- \u0026#34;\u0026#34;\u0026#34; @Author: Kang @Modified by: @Datetime: 2025/07/15 13:57 @Description: OCR 测试脚本 \u0026#34;\u0026#34;\u0026#34; import os import time import copy from pathlib import Path from typing import List, Dict, Any import cv2 import numpy as np from loguru import logger from paddlex import create_model class OCRProcessor: \u0026#34;\u0026#34;\u0026#34;OCR处理器类\u0026#34;\u0026#34;\u0026#34; def __init__(self, det_model_dir: str = \u0026#34;/opt/models/ocr/PP-OCRv4_server_det_infer_om_310P\u0026#34;, rec_model_dir: str = \u0026#34;/opt/models/ocr/PP-OCRv4_server_rec_infer_om_310P\u0026#34;, ori_model_dir: str = \u0026#34;/opt/models/ocr/PP-LCNet_x1_0_textline_ori_infer\u0026#34;, device: str = \u0026#34;npu:0\u0026#34;, output_dir: str = \u0026#34;/opt/output/\u0026#34;): self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) # 配置参数 hpi_config = { \u0026#34;auto_config\u0026#34;: False, \u0026#34;backend\u0026#34;: \u0026#34;om\u0026#34;, } # 初始化分类模型 logger.info(\u0026#34;正在加载分类模型...\u0026#34;) self.model_ori = create_model( model_name=\u0026#34;PP-LCNet_x1_0_textline_ori\u0026#34;, model_dir=ori_model_dir, ) # 初始化检测模型 logger.info(\u0026#34;正在加载检测模型...\u0026#34;) self.model_det = create_model( model_name=\u0026#34;PP-OCRv4_server_det\u0026#34;, model_dir=det_model_dir, device=device, use_hpip=True, hpi_config=hpi_config, input_shape=[3, 640, 480] ) # 初始化识别模型 logger.info(\u0026#34;正在加载识别模型...\u0026#34;) self.model_rec = create_model( model_name=\u0026#34;PP-OCRv4_server_rec\u0026#34;, model_dir=rec_model_dir, device=device, use_hpip=True, hpi_config=hpi_config, input_shape=[3, 48, 320] ) logger.info(\u0026#34;模型加载完成\u0026#34;) def _crop_by_polys(self, img: np.ndarray, dt_polys: List[list]) -\u0026gt; List[dict]: \u0026#34;\u0026#34;\u0026#34; 根据检测框裁剪图片 Args: img (nd.ndarray): 输入图片 dt_polys (list[list]): 检测框列表 Returns: list[dict]: 裁剪后的图片列表 Raises: NotImplementedError: 如果 det_box_type 不是 \u0026#39;quad\u0026#39; 或 \u0026#39;poly\u0026#39; \u0026#34;\u0026#34;\u0026#34; dt_boxes = np.array(dt_polys) output_list = [] for bno in range(len(dt_boxes)): tmp_box = copy.deepcopy(dt_boxes[bno]) img_crop = self.get_minarea_rect_crop(img, tmp_box) output_list.append(img_crop) return output_list def get_minarea_rect_crop(self, img: np.ndarray, points: np.ndarray) -\u0026gt; np.ndarray: \u0026#34;\u0026#34;\u0026#34; 根据给定的图片和点获取最小面积矩形裁剪 Args: img (np.ndarray): 输入图片 points (np.ndarray): 定义要裁剪的形状的点列表 Returns: np.ndarray: 最小面积矩形裁剪后的图片 \u0026#34;\u0026#34;\u0026#34; bounding_box = cv2.minAreaRect(np.array(points).astype(np.int32)) points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0]) index_a, index_b, index_c, index_d = 0, 1, 2, 3 if points[1][1] \u0026gt; points[0][1]: index_a = 0 index_d = 1 else: index_a = 1 index_d = 0 if points[3][1] \u0026gt; points[2][1]: index_b = 2 index_c = 3 else: index_b = 3 index_c = 2 box = [points[index_a], points[index_b], points[index_c], points[index_d]] crop_img = self.get_rotate_crop_image(img, np.array(box)) return crop_img def _rotate_image(self, image_array_list: List[np.ndarray], rotate_angle_list: List[int]): assert len(image_array_list) == len( rotate_angle_list ), f\u0026#34;Length of image ({len(image_array_list)}) must match length of angle ({len(rotate_angle_list)})\u0026#34; for angle in rotate_angle_list: assert angle in [0, 1], f\u0026#34;rotate_angle must be 0 or 1, now it\u0026#39;s {angle}\u0026#34; rotated_images = [] for image_array, rotate_indicator in zip(image_array_list, rotate_angle_list): # Convert 0/1 indicator to actual rotation angle rotate_angle = rotate_indicator * 180 if rotate_angle \u0026lt; 0 or rotate_angle \u0026gt;= 360: raise ValueError(\u0026#34;`angle` should be in range [0, 360)\u0026#34;) if rotate_angle \u0026lt; 1e-7: rotated_images.append(image_array) continue # Should we align corners? h, w = image_array.shape[:2] center = (w / 2, h / 2) scale = 1.0 mat = cv2.getRotationMatrix2D(center, rotate_angle, scale) cos = np.abs(mat[0, 0]) sin = np.abs(mat[0, 1]) new_w = int((h * sin) + (w * cos)) new_h = int((h * cos) + (w * sin)) mat[0, 2] += (new_w - w) / 2 mat[1, 2] += (new_h - h) / 2 dst_size = (new_w, new_h) rotated = cv2.warpAffine( image_array, mat, dst_size, flags=cv2.INTER_CUBIC, ) rotated_images.append(rotated) logger.info(f\u0026#34;旋转后的图片数量: {len(rotated_images)}\u0026#34;) return rotated_images def get_rotate_crop_image(self, img: np.ndarray, points: list) -\u0026gt; np.ndarray: \u0026#34;\u0026#34;\u0026#34; 根据给定的四个点裁剪并旋转输入图片，形成透视变换后的图片 Args: img (np.ndarray): 输入图片数组 points (list): 定义裁剪区域的四个2D点列表 Returns: np.ndarray: 变换后的图片数组 \u0026#34;\u0026#34;\u0026#34; assert len(points) == 4, \u0026#34;shape of points must be 4*2\u0026#34; img_crop_width = int( max( np.linalg.norm(points[0] - points[1]), np.linalg.norm(points[2] - points[3]), ) ) img_crop_height = int( max( np.linalg.norm(points[0] - points[3]), np.linalg.norm(points[1] - points[2]), ) ) pts_std = np.float32( [ [0, 0], [img_crop_width, 0], [img_crop_width, img_crop_height], [0, img_crop_height], ] ) M = cv2.getPerspectiveTransform(points, pts_std) dst_img = cv2.warpPerspective( img, M, (img_crop_width, img_crop_height), borderMode=cv2.BORDER_REPLICATE, flags=cv2.INTER_CUBIC, ) dst_img_height, dst_img_width = dst_img.shape[0:2] if dst_img_height * 1.0 / dst_img_width \u0026gt;= 1.5: dst_img = np.rot90(dst_img) return dst_img def process_image(self, image_path: str) -\u0026gt; List[Dict[str, Any]]: \u0026#34;\u0026#34;\u0026#34; 处理单张图片进行OCR识别 Args: image_path: 图片路径 Returns: 识别结果列表 \u0026#34;\u0026#34;\u0026#34; if not os.path.exists(image_path): logger.error(f\u0026#34;图片文件不存在: {image_path}\u0026#34;) return [] try: # 读取图片 image = cv2.imread(image_path) if image is None: logger.error(f\u0026#34;无法读取图片: {image_path}\u0026#34;) return [] logger.info(f\u0026#34;开始处理图片: {image_path}\u0026#34;) start_time = time.time() # 文本检测 logger.info(f\u0026#34;开始文本检测\u0026#34;) det_start = time.time() output_det = self.model_det.predict(image) det_time = time.time() - det_start logger.info(f\u0026#34;检测耗时-文本检测: {det_time}s\u0026#34;) results = [] result_det = [] for res in output_det: det_polys = res.get(\u0026#34;dt_polys\u0026#34;, []) det_scores = res.get(\u0026#34;dt_scores\u0026#34;, []) for idx, det_poly in enumerate(det_polys): result_det.append({ \u0026#34;idx\u0026#34;: idx, \u0026#34;dt_polys\u0026#34;: det_poly, \u0026#34;dt_scores\u0026#34;: det_scores[idx], }) logger.info(f\u0026#34;检测到 {len(det_polys)} 个文本区域\u0026#34;) images_det = self._crop_by_polys(image, det_polys) for idx, img in enumerate(images_det): cv2.imwrite(f\u0026#34;{self.output_dir}/cropped_det_{idx}.png\u0026#34;, img) # 文本分类-方向判断 logger.info(f\u0026#34;开始文本方向分类\u0026#34;) ori_start = time.time() output_ori = self.model_ori.predict(images_det) ori_time = time.time() - ori_start logger.info(f\u0026#34;检测耗时-文本分类: {ori_time}s\u0026#34;) angles = [ int(ori_res[\u0026#34;class_ids\u0026#34;][0]) for ori_res in output_ori ] images_ori = self._rotate_image(images_det, angles) for idx, img in enumerate(images_ori): cv2.imwrite(f\u0026#34;{self.output_dir}/cropped_ori_{idx}.png\u0026#34;, img) # 文本识别 logger.info(f\u0026#34;开始文本识别\u0026#34;) rec_start = time.time() for item in result_det: output_rec = self.model_rec.predict(images_ori[item[\u0026#34;idx\u0026#34;]]) for rec_res in output_rec: rec_text = rec_res.get(\u0026#34;rec_text\u0026#34;, \u0026#34;\u0026#34;) rec_score = rec_res.get(\u0026#34;rec_score\u0026#34;, 0) results.append({ \u0026#34;idx\u0026#34;: item[\u0026#34;idx\u0026#34;], \u0026#34;dt_polys\u0026#34;: item[\u0026#34;dt_polys\u0026#34;].tolist(), \u0026#34;dt_scores\u0026#34;: item[\u0026#34;dt_scores\u0026#34;], \u0026#34;rec_res\u0026#34;: rec_text, \u0026#34;rec_score\u0026#34;: rec_score, }) rec_time = time.time() - rec_start logger.info(f\u0026#34;检测耗时-文本识别: {rec_time}s\u0026#34;) total_time = time.time() - start_time logger.info(f\u0026#34;总处理耗时: {total_time:.3f}s\u0026#34;) return results except Exception as e: logger.exception(f\u0026#34;处理图片失败: {e}\u0026#34;) return [] def batch_process(self, image_paths: List[str]) -\u0026gt; Dict[str, List[Dict[str, Any]]]: \u0026#34;\u0026#34;\u0026#34; 批量处理图片 Args: image_paths: 图片路径列表 Returns: 批量处理结果 \u0026#34;\u0026#34;\u0026#34; batch_results = {} for image_path in image_paths: results = self.process_image(image_path) batch_results[image_path] = results return batch_results def main(): \u0026#34;\u0026#34;\u0026#34;主函数\u0026#34;\u0026#34;\u0026#34; # 初始化OCR处理器 ocr_processor = OCRProcessor( output_dir=\u0026#34;/opt/output/\u0026#34; ) # 处理单张图片 image_path = \u0026#34;/opt/test/test.png\u0026#34; results = ocr_processor.process_image(image_path) # 输出结果 print(\u0026#34;\\n=== OCR 识别结果 ===\u0026#34;) for result in results: print(result) if __name__ == \u0026#34;__main__\u0026#34;: main() 支持的模型\r模型类型 模型名称 输入形状 芯片支持 文本检测 PP-OCRv4_mobile_det (1,3,640,480) 910B/310P/310B 文本识别 PP-OCRv4_mobile_rec (1,3,48,320) 910B/310P/310B 图像分类 ResNet50 (1,3,224,224) 910B/310P/310B 目标检测 RT-DETR-L 多输入 910B/310P/310B 总结与建议\r方案对比\r方案五（PaddleX）：✅ 强烈推荐，官方支持，性能优秀，稳定可靠 方案三（ONNX Runtime）：⚠️ 可用但性能一般，适合快速验证 方案四（OM 模型）：⚠️ 技术难度高，适合深度定制 方案一、二：❌ 不推荐，存在较多问题 最佳实践\r优先选择方案五：使用 PaddleX 高性能推理插件 固定输入形状：避免动态 Shape 带来的复杂性 模型预热：推理前进行 warm-up 操作 监控性能：使用昇腾 CANN Profiling 工具优化性能 版本管理：保持 CANN 工具链和 PaddleX 版本同步更新 性能优化建议\r使用 FP16 精度提升推理速度 合理配置批处理大小 利用算子缓存机制 定期清理 kernel_meta 临时文件 通过本文的详细介绍，开发者可以根据实际需求选择合适的部署方案，并参考相应的问题解决方法，在昇腾 310P 平台上成功部署 PaddlePaddle 模型。\n","date":"2025-06-11T00:00:00Z","image":"/p/paddle_on_ascend310p/paddle_on_ascend310P.png","permalink":"/p/paddle_on_ascend310p/","title":"PaddlePaddle 在昇腾 310P 上的部署实践"},{"content":"在计算机发展的历史长河中，启动固件扮演着至关重要的角色。从早期的 BIOS 到如今功能强大的 UEFI，它不断进化以满足硬件日益增长的性能、安全与管理需求。\n缘起：BIOS 的黄金时代\r1980 年代，IBM PC 推出 BIOS（Basic Input/Output System），它被刻录在主板 ROM 中，承担了计算机启动的三大要务：\n自检（POST）：检测 CPU、内存、显卡、键盘等硬件是否可用。 MBR 加载与硬件中断接口：从硬盘第一个扇区（Master Boot Record，512 字节）读取初级 Bootloader，并通过 INT 13h/INT 10h 等中断调用提供统一硬件访问能力。 尽管 BIOS 架构简单、兼容性高，却在以下方面遇到瓶颈：\n磁盘容量限制：MBR 最多支持 2 TB，且最多 4 个主分区。 固件更新困难：驱动代码写死在 ROM，无法灵活扩展或修补。 图形与网络能力匮乏：仅提供最基础的文本界面与简单的 PXE 网络微码加载。 颠覆：UEFI 的登场\r进入 21 世纪，Intel 发起了 UEFI（Unified Extensible Firmware Interface）联盟，旨在打造更灵活、可扩展、安全的启动环境。\nGPT 分区支持：突破 TB 级存储限制，可管理数百个分区。 模块化驱动：通过可加载的 .efi 驱动扩展硬件支持，无需刷 ROM。 现代图形界面：支持 GUI、鼠标、多语言，用户交互更友好。 安全与远程管理： 验证各阶段引导程序与操作系统签名，防止篡改与 Rootkit（Secure Boot）。 内置 HTTP/FTP 客户端与 UEFI Shell，可远端更新或执行脚本。 UEFI 启动流程简述\r固件并行初始化：多线程加载硬件驱动与固件组件。 扫描 ESP：读取 EFI 系统分区（FAT32 格式），查找 �EFI\\ 路径下的 .efi 文件。 运行 Boot Manager：根据 NVRAM 存储的引导选项顺序执行 EFI 应用。 加载内核：EFI 应用（如 bootx64.efi）引导操作系统内核，并将控制权交给它。 对比小结\r特性 BIOS + MBR UEFI + GPT 磁盘支持 ≤ 2 TB；最多 4 分区 理论上 ≥ 9.4 ZB；最多 128 分区 驱动与扩展 ROM 固化，不易更新 动态加载 .efi 模块，易升级 启动界面 文本或极简图形 丰富 GUI、鼠标、多语言支持 安全启动 不支持 支持签名验证，防篡改 网络功能 仅 PXE 简单引导 支持 HTTP/FTP，远程管理 现实世界的选择与迁移\r老旧设备 \u0026amp; 嵌入式系统：BIOS+MBR 因体积小、兼容性强依旧有效。 现代 PC \u0026amp; 服务器：UEFI+GPT 标配，提升启动速度、安全性与大盘支持。 双系统／多重引导：推荐统一 UEFI 模式，Windows 用 BCD，Linux 用 shim+GRUB EFI，以减少启动冲突。 迁移要点：\nBIOS → UEFI：备份数据，使用 Windows mbr2gpt 或 Linux gdisk 转换 GPT，创建 ≥100 MB 的 ESP，切换固件模式并重建引导。 UEFI → BIOS：将 GPT 转为 MBR（注意丢失 GPT 信息），重新写入传统 MBR 引导代码，调整固件为 Legacy 模式。 常见启动问题与排查技巧\r无法识别 ESP 检查分区类型：ESP 必须设置为 EF00（GPT）或 FAT32（MBR）。 验证文件：确认 �EFI\\BOOT\\BOOTX64.EFI（或对应平台）存在。 Secure Boot 报错 关闭或配置：进入固件设置，临时禁用 Secure Boot，或导入正确的公钥（PK/KEK）。 引导选项丢失 使用 efibootmgr（Linux）或 bcdedit（Windows）重新创建引导条目。 硬盘切换模式后无法启动 检查控制器模式：IDE/RAID/AHCI 切换后需同步更新内核模块和驱动签名。 高级自定义与扩展\rUEFI Shell 自动化：编写 .nsh 脚本，自动挂载分区、执行自检或远程下载固件更新。 驱动注入：将厂商 .efi 驱动放入 ESP，可扩展对 NVMe、RAID 控制器等的原生支持。 变量管理：利用 dmpstore（Shell）或 efivar 工具读写固件变量，实现自动化启动或日志收集。 开放固件替代品：如 TianoCore（OVMF）、Coreboot，可根据需要定制体积与功能。 ","date":"2025-04-23T00:00:00Z","image":"/p/bios_and_uefi/cover.png","permalink":"/p/bios_and_uefi/","title":"BIOS 与 UEFI：从历史到现代的引导之路"},{"content":"简介\rHelm 是一个强大的工具，帮助你管理 Kubernetes 应用程序。通过 Helm Charts，可以定义、安装和升级即使是最复杂的 Kubernetes 应用程序。\nHelm Charts 易于创建、版本控制、共享和发布。这意味着你可以更高效地使用 Helm，避免手动复制粘贴配置文件的繁琐过程。\nHelm 已成为 CNCF（云原生计算基金会）的毕业项目，并由 Helm 社区持续维护，确保其稳定性和持续发展。\n管理复杂性 ​\tHelm Charts 可以描述即使是最复杂的应用程序，提供可重复的应用安装流程，并作为应用配置的单一权威来源，确保一致性和可靠性。\n轻松更新 ​\t使用 Helm，你可以通过原地升级和自定义钩子，轻松管理应用的更新过程，减少因更新带来的困扰。\n简单共享 ​\tHelm Charts 易于版本控制、共享，并且可以托管在公共或私有服务器上，方便团队协作和应用分发。\n回滚 ​\t如果更新出现问题，使用 helm rollback 命令可以轻松回滚到之前的发布版本，确保应用的稳定运行。\nHelm 的工作原理\r结合以上概念，Helm 的工作流程可以这样理解：\n安装 Charts： Helm 将 Charts 安装到 Kubernetes 集群中，每次安装都会创建一个新的 Release。 管理 Releases： 每个 Release 独立管理，允许同一个 Chart 被多次部署，满足不同的需求。 查找 Charts： 通过搜索 Helm 的 Chart Repository，可以找到并获取新的 Charts 来部署。 整体图表示意：\n1 2 3 4 5 6 7 8 9 10 11 12 13 +---------------------+ +---------------------+ | Chart Repository | \u0026lt;------\u0026gt; | Helm | | (存放所有 Charts) | | (安装和管理 Charts) | +---------------------+ +---------+-----------+ | v +--------------------+ | Kubernetes Cluster | | ------------------ | | Release A | | Release B | | ... | +--------------------+ 三个核心概念\rChart（图表）\rChart 是 Helm 的一个包。它包含了在 Kubernetes 集群中运行一个应用、工具或服务所需的所有资源定义。可以将其类比为 Kubernetes 版本的 Homebrew 配方、Apt 的 dpkg 或 Yum 的 RPM 文件。\n图表示意：\n1 2 3 4 5 6 7 8 +-----------------+ | Chart | | --------------- | | Deployment.yaml | | Service.yaml | | ConfigMap.yaml | | ... | +-----------------+ Helm 会按照一下顺序部署：\nNamespace \u0026mdash;\u0026gt; NetworkPolicy \u0026mdash;\u0026gt; ResourceQuota \u0026mdash;\u0026gt; LimitRange \u0026mdash;\u0026gt; PodSecurityPolicy \u0026mdash;\u0026gt; PodDisruptionBudget \u0026mdash;\u0026gt; ServiceAccount \u0026mdash;\u0026gt; Secret \u0026mdash;\u0026gt; SecretList \u0026mdash;\u0026gt; ConfigMap \u0026mdash;\u0026gt; StorageClass \u0026mdash;\u0026gt; PersistentVolume \u0026mdash;\u0026gt; PersistentVolumeClaim \u0026mdash;\u0026gt; CustomResourceDefinition \u0026mdash;\u0026gt; ClusterRole \u0026mdash;\u0026gt; ClusterRoleList \u0026mdash;\u0026gt; ClusterRoleBinding \u0026mdash;\u0026gt; ClusterRoleBindingList \u0026mdash;\u0026gt; Role \u0026mdash;\u0026gt; RoleList \u0026mdash;\u0026gt; RoleBinding \u0026mdash;\u0026gt; RoleBindingList \u0026mdash;\u0026gt; Service \u0026mdash;\u0026gt; DaemonSet \u0026mdash;\u0026gt; Pod \u0026mdash;\u0026gt; ReplicationController \u0026mdash;\u0026gt; ReplicaSet \u0026mdash;\u0026gt; Deployment \u0026mdash;\u0026gt; HorizontalPodAutoscaler \u0026mdash;\u0026gt; StatefulSet \u0026mdash;\u0026gt; Job \u0026mdash;\u0026gt; CronJob \u0026mdash;\u0026gt; Ingress \u0026mdash;\u0026gt; APIService\nRepository（仓库）\rRepository 是存放和共享 Charts 的地方。它类似于 Perl 的 CPAN 存档或 Fedora 的软件包数据库，但专门用于 Kubernetes 的包。\n图表示意：\n1 2 3 4 5 6 7 +---------------------+ | Chart Repository | | ------------------- | | Chart A Chart B | | Chart C Chart D | | ... | +---------------------+ Release（发布版本）\rRelease 是在 Kubernetes 集群中运行的 Chart 的一个实例。一个 Chart 通常可以在同一个集群中安装多次。每次安装都会创建一个新的 Release。例如，假设有一个 MySQL Chart。如果你希望在集群中运行两个数据库实例，可以安装该 Chart 两次，每次都会有自己独立的 Release 和 Release 名称。\n图表示意：\n1 2 3 4 5 6 7 +------------------+ +------------------+ | Release A | | Release B | |------------------| |------------------| | MySQL Chart | | MySQL Chart | | Release Name: A | | Release Name: B | | ... | | ... | +------------------+ +------------------+ 安装\rEvery release of Helm provides binary releases for a variety of OSes. These binary versions can be manually downloaded and installed.\nDownload your desired version Unpack it (tar -zxvf helm-v3.0.0-linux-amd64.tar.gz) Find the helm binary in the unpacked directory, and move it to its desired destination (mv linux-amd64/helm /usr/local/bin/helm) 创建项目模板\r通过命令可以创建一个 Helm 项目模板。\n1 helm create mychart 生成以下文件和目录：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 . ├── charts # 存放依赖的 Charts ├── Chart.yaml # 描述 Chart 的元数据 ├── templates # 存放模板文件 │ ├── deployment.yaml # 部署模板 │ ├── _helpers.tpl # 辅助模板 │ ├── hpa.yaml # 水平 Pod 自动缩放模板 │ ├── ingress.yaml # 入口模板 │ ├── NOTES.txt # 安装说明 │ ├── serviceaccount.yaml # 服务账户模板 │ ├── service.yaml # 服务模板 │ └── tests # 测试模板 │ └── test-connection.yaml # 测试连接模板 └── values.yaml # 默认值，可以覆盖模板中的值 总结\rHelm 通过 Charts、Repositories 和 Releases 三个核心概念，简化了在 Kubernetes 集群中应用、工具和服务的部署与管理。Charts 提供了应用的定义，Repositories 便于 Charts 的共享和分发，而 Releases 则负责在集群中具体运行和管理这些应用实例。\n","date":"2025-01-18T00:00:00Z","permalink":"/p/kubernetes_helm_guide/","title":"Helm 核心概念与实践指南"},{"content":"为什么需要屏蔽国外IP\r公网服务器面临的安全威胁日益严峻：\n互联网上有大量扫描器24小时不间断扫描服务器，试图获取权限控制您的系统 服务器日志分析表明大多数攻击来源于国外服务器，如荷兰、美国、新加坡、日本等国家 不论是云服务器还是IDC机房托管的服务器，只要对外提供服务就会暴露端口，增加安全风险 解决方案概述\r对于主要面向国内用户的服务，我们可以通过屏蔽国外IP访问来显著提升安全性。\n技术原理\rIptables：Linux系统防火墙工具，用于过滤和拦截请求 Ipset模块：Iptables的扩展，支持高效匹配大批量IP地址段 IPdeny：提供定期更新的全球IP地址分配数据 实现思路\r收集并整理国内IP地址段到Ipset中 配置Iptables调用Ipset模块检查来源IP 允许国内IP访问，拒绝国外IP连接 完整实施步骤\r本指南基于CentOS 7.6环境，不同Linux版本的命令可能有所差异\n安装必要工具\r1 2 # 如果尚未安装ipset yum install -y ipset 创建IP地址集合\r下载中国IP地址段\r1 wget http://www.ipdeny.com/ipblocks/data/countries/cn.zone 转换为Ipset指令\r1 2 for i in `cat cn.zone`; do echo \u0026#34;ipset add china $i\u0026#34; \u0026gt;\u0026gt;ipset_result.sh; done chmod +x ipset_result.sh 创建并填充Ipset集合\r1 2 3 4 5 6 7 8 9 10 # 创建china集合 ipset create china hash:net hashsize 10000 maxelem 1000000 # 添加局域网IP地址段 echo \u0026#34;ipset add china 10.0.0.0/8\u0026#34; \u0026gt;\u0026gt; ipset_result.sh echo \u0026#34;ipset add china 172.0.0.0/8\u0026#34; \u0026gt;\u0026gt; ipset_result.sh echo \u0026#34;ipset add china 192.0.0.0/8\u0026#34; \u0026gt;\u0026gt; ipset_result.sh # 执行脚本添加IP段 bash ipset_result.sh 验证IP集合\r1 2 ipset list china ipset list china | wc -l # 应有约8000多条数据 配置Iptables规则\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 清除现有规则（如果需要） iptables -F iptables -X # 创建基本规则 cat \u0026gt; /etc/sysconfig/iptables \u0026lt;\u0026lt; EOF *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -A INPUT -p icmp -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT # 如需开放其他端口，请在此添加规则 # 例如: -A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT -A INPUT -m set ! --match-set china src -j DROP -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT EOF # 应用规则 iptables-restore \u0026lt; /etc/sysconfig/iptables 确保配置持久化\r为防止服务器重启后配置丢失，需要进行持久化设置：\n持久化Ipset数据\r1 2 3 4 5 6 # 保存Ipset数据 ipset save china \u0026gt; /etc/ipset.conf # 配置启动时加载 chmod +x /etc/rc.d/rc.local echo \u0026#34;ipset restore \u0026lt; /etc/ipset.conf\u0026#34; \u0026gt;\u0026gt; /etc/rc.d/rc.local 持久化Iptables规则\r1 2 # 配置启动时加载 echo \u0026#34;/usr/sbin/iptables-restore \u0026lt; /etc/sysconfig/iptables\u0026#34; \u0026gt;\u0026gt; /etc/rc.d/rc.local 自动更新IP地址段\r为确保IP地址段保持最新，可以设置定期更新：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 创建每周自动更新脚本 cat \u0026gt; /usr/local/bin/update_cn_ip.sh \u0026lt;\u0026lt; EOF #!/bin/bash wget -O /tmp/cn.zone http://www.ipdeny.com/ipblocks/data/countries/cn.zone ipset flush china for ip in \\$(cat /tmp/cn.zone); do ipset add china \\$ip; done # 添加局域网IP段 ipset add china 10.0.0.0/8 ipset add china 172.0.0.0/8 ipset add china 192.0.0.0/8 # 更新持久化文件 ipset save china \u0026gt; /etc/ipset.conf EOF chmod +x /usr/local/bin/update_cn_ip.sh # 添加每周执行的定时任务 echo \u0026#34;0 0 * * 1 /usr/local/bin/update_cn_ip.sh\u0026#34; \u0026gt; /etc/cron.d/update_cn_ip 验证与故障排除\r测试配置\r1 2 3 4 5 6 7 8 # 检查Ipset集合 ipset list china # 检查Iptables规则 iptables -L -n # 测试国内IP访问（应该可以访问） # 测试国外IP访问（应该被阻止） 常见问题解决\r无法SSH连接：确保在添加拦截规则前先添加了SSH端口规则 局域网访问受限：检查是否添加了私有IP段到china集合 配置未持久化：检查rc.local文件权限和脚本内容 结论\r通过屏蔽国外IP访问，我们可以大幅降低服务器被攻击的风险，特别适合主要面向国内用户的服务。需要注意的是，此方法可能会影响到海外用户的合法访问，请根据实际业务需求进行调整。\n","date":"2025-01-01T00:00:00Z","image":"/p/block_foreign_ips_server_security/cover_chinese.png","permalink":"/p/block_foreign_ips_server_security/","title":"屏蔽国外IP访问服务器完整指南"},{"content":"部署\r请确保已经部署\ncert-manager：Installation - cert-manager Documentation 1 kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.yaml cert-manager-webhook-dnspod：imroc/cert-manager-webhook-dnspod: cert-manager webhook resolver for DNSPod (github.com) 1 kubectl apply -f https://raw.githubusercontent.com/imroc/cert-manager-webhook-dnspod/master/bundle.yaml 修改部署文件ingress-dnspod-solver.yaml以下内容：\ningress-dnspod-solver.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 apiVersion: v1 kind: ServiceAccount metadata: name: ingress-dnspod-solver namespace: cert-manager labels: app: ingress-dnspod-solver --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: ingress-dnspod-solver rules: - apiGroups: [ \u0026#34;networking.k8s.io\u0026#34; ] resources: [ \u0026#34;ingresses\u0026#34; ] verbs: [ \u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34; ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: ingress-dnspod-solver labels: app: ingress-dnspod-solver roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: ingress-dnspod-solver subjects: - apiGroup: \u0026#34;\u0026#34; kind: ServiceAccount name: ingress-dnspod-solver namespace: cert-manager --- apiVersion: v1 kind: Secret metadata: name: ingress-dnspod-solver namespace: cert-manager type: Opaque stringData: TENCENT_SECRET_KEY: \u0026#34;\u0026lt;secret_key\u0026gt;\u0026#34; # 腾讯云密钥 --- apiVersion: v1 kind: ConfigMap metadata: name: ingress-dnspod-solver namespace: cert-manager data: DOMAIN: \u0026#34;example.com\u0026#34; # 域名 POLICY: \u0026#34;retain\u0026#34; # 监听处置策略：retain(当资源更新时保留记录)、update(当资源更新时更新记录) TENCENT_SECRET_ID: \u0026#34;\u0026lt;secret_id\u0026gt;\u0026#34; # 腾讯云密钥 RECORD_VALUE: \u0026#34;\u0026lt;record_value\u0026gt;\u0026#34; # 解析值 --- apiVersion: apps/v1 kind: Deployment metadata: name: ingress-dnspod-solver namespace: cert-manager labels: app: ingress-dnspod-solver spec: replicas: 1 selector: matchLabels: app: ingress-dnspod-solver template: metadata: labels: app: ingress-dnspod-solver spec: serviceAccountName: ingress-dnspod-solver containers: - name: ingress-dnspod-solver image: harbor.example.com/devops/ingress-dnspod-solver:latest imagePullPolicy: IfNotPresent env: - name: DOMAIN valueFrom: configMapKeyRef: name: ingress-dnspod-solver key: DOMAIN - name: POLICY valueFrom: configMapKeyRef: name: ingress-dnspod-solver key: POLICY - name: TENCENT_SECRET_ID valueFrom: configMapKeyRef: name: ingress-dnspod-solver key: TENCENT_SECRET_ID - name: TENCENT_SECRET_KEY valueFrom: secretKeyRef: name: ingress-dnspod-solver key: TENCENT_SECRET_KEY - name: RECORD_VALUE valueFrom: configMapKeyRef: name: ingress-dnspod-solver key: RECORD_VALUE 1 kubectl apply -f ingress-dnspod-solver.yaml 源码分析构建\r源码采用 go 语言编写，使用 go mod 管理依赖。\nmain.go\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common\u0026#34; \u0026#34;github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile\u0026#34; dnspod \u0026#34;github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323\u0026#34; \u0026#34;go.uber.org/zap\u0026#34; networkingv1 \u0026#34;k8s.io/api/networking/v1\u0026#34; \u0026#34;k8s.io/client-go/informers\u0026#34; \u0026#34;k8s.io/client-go/kubernetes\u0026#34; \u0026#34;k8s.io/client-go/rest\u0026#34; \u0026#34;k8s.io/client-go/tools/cache\u0026#34; \u0026#34;k8s.io/client-go/tools/clientcmd\u0026#34; \u0026#34;os\u0026#34; ) var ( logger *zap.Logger version string policy string domain string secretId string secretKey string recordValue string dnsPodClient *dnspod.Client clientSet *kubernetes.Clientset ) func initLogger() { logger, _ = zap.NewDevelopment() defer func(logger *zap.Logger) { err := logger.Sync() if err != nil { fmt.Println(\u0026#34;Logger sync error: \u0026#34;, err) } }(logger) } func initCheck() { version = os.Getenv(\u0026#34;VERSION\u0026#34;) if version == \u0026#34;\u0026#34; { version = \u0026#34;2021-03-23\u0026#34; } policy = os.Getenv(\u0026#34;POLICY\u0026#34;) if policy == \u0026#34;\u0026#34; { policy = \u0026#34;retain\u0026#34; } domain = os.Getenv(\u0026#34;DOMAIN\u0026#34;) if domain == \u0026#34;\u0026#34; { logger.Error(\u0026#34;Please set the environment variable `DOMAIN`\u0026#34;) os.Exit(1) } secretId = os.Getenv(\u0026#34;TENCENT_SECRET_ID\u0026#34;) secretKey = os.Getenv(\u0026#34;TENCENT_SECRET_KEY\u0026#34;) if secretId == \u0026#34;\u0026#34; || secretKey == \u0026#34;\u0026#34; { logger.Error(\u0026#34;Please set the environment variables `TENCENT_SECRET_ID` and `TENCENT_SECRET_KEY`\u0026#34;) os.Exit(1) } recordValue = os.Getenv(\u0026#34;RECORD_VALUE\u0026#34;) if recordValue == \u0026#34;\u0026#34; { logger.Error(\u0026#34;Please set the environment variable `RECORD_VALUE`\u0026#34;) os.Exit(1) } logger.Info(\u0026#34;---------------------------------\u0026#34;) logger.Info(\u0026#34;Version\u0026#34; + \u0026#34;: \u0026#34; + version) logger.Info(\u0026#34;Policy\u0026#34; + \u0026#34;: \u0026#34; + policy) logger.Info(\u0026#34;Domain\u0026#34; + \u0026#34;: \u0026#34; + domain) logger.Info(\u0026#34;SecretId\u0026#34; + \u0026#34;: \u0026#34; + secretId) logger.Info(\u0026#34;SecretKey: *****\u0026#34;) logger.Info(\u0026#34;RecordValue\u0026#34; + \u0026#34;: \u0026#34; + recordValue) logger.Info(\u0026#34;---------------------------------\u0026#34;) } func getRecordDict() map[string]uint64 { recordDict := make(map[string]uint64) request := dnspod.NewDescribeRecordListRequest() request.Domain = common.StringPtr(domain) request.RecordType = common.StringPtr(\u0026#34;A\u0026#34;) request.Offset = common.Uint64Ptr(0) request.Limit = common.Uint64Ptr(10) // 返回的 response 是一个 DescribeRecordListResponse 的实例，与请求对象对应 response, err := dnsPodClient.DescribeRecordList(request) if err != nil { logger.Panic(err.Error()) } for i := 0; i \u0026lt; len(response.Response.RecordList); i++ { recordDict[*response.Response.RecordList[i].Name] = *response.Response.RecordList[i].RecordId } domainCount := *response.Response.RecordCountInfo.ListCount domainTotal := *response.Response.RecordCountInfo.TotalCount for domainCount \u0026lt; domainTotal { request.Offset = common.Uint64Ptr(domainCount) response, err = dnsPodClient.DescribeRecordList(request) if err != nil { logger.Panic(err.Error()) } for i := 0; i \u0026lt; len(response.Response.RecordList); i++ { recordDict[*response.Response.RecordList[i].Name] = *response.Response.RecordList[i].RecordId } domainCount += *response.Response.RecordCountInfo.ListCount } logger.Info(\u0026#34;RecordDict: \u0026#34;, zap.Any(\u0026#34;RecordDict\u0026#34;, recordDict)) return recordDict } func createRecord(subDomain string) { request := dnspod.NewCreateRecordRequest() request.Domain = common.StringPtr(domain) request.SubDomain = common.StringPtr(subDomain) request.RecordType = common.StringPtr(\u0026#34;A\u0026#34;) request.RecordLine = common.StringPtr(\u0026#34;默认\u0026#34;) request.Value = common.StringPtr(recordValue) _, err := dnsPodClient.CreateRecord(request) if err != nil { logger.Panic(err.Error()) } } func updateRecord(recordId uint64, subDomain string) { request := dnspod.NewModifyRecordRequest() request.Domain = common.StringPtr(domain) request.RecordId = common.Uint64Ptr(recordId) request.SubDomain = common.StringPtr(subDomain) request.RecordType = common.StringPtr(\u0026#34;A\u0026#34;) request.RecordLine = common.StringPtr(\u0026#34;默认\u0026#34;) request.Value = common.StringPtr(recordValue) _, err := dnsPodClient.ModifyRecord(request) if err != nil { logger.Panic(err.Error()) } } func deleteRecord(recordId uint64) { request := dnspod.NewDeleteRecordRequest() request.Domain = common.StringPtr(domain) request.RecordId = common.Uint64Ptr(recordId) _, err := dnsPodClient.DeleteRecord(request) if err != nil { logger.Panic(err.Error()) } } func addHandler(obj interface{}) { logger.Info(\u0026#34;Detect Ingress Add Event\u0026#34;) recordDict := getRecordDict() addIngress := obj.(*networkingv1.Ingress) for i := 0; i \u0026lt; len(addIngress.Spec.Rules); i++ { customDomain := addIngress.Spec.Rules[i].Host // customDomain 判断是否以 domain 结尾 if customDomain[len(customDomain)-len(domain):] != domain { continue } subDomain := customDomain[:len(customDomain)-len(domain)-1] if _, ok := recordDict[subDomain]; !ok { createRecord(subDomain) } else { if policy == \u0026#34;update\u0026#34; { updateRecord(recordDict[subDomain], subDomain) } } } } func updateHandler(oldObj, newObj interface{}) { logger.Info(\u0026#34;Detect Ingress Update Event\u0026#34;) recordDict := getRecordDict() newIngress := newObj.(*networkingv1.Ingress) for i := 0; i \u0026lt; len(newIngress.Spec.Rules); i++ { customDomain := newIngress.Spec.Rules[i].Host // customDomain 判断是否以 domain 结尾 if customDomain[len(customDomain)-len(domain):] != domain { continue } subDomain := customDomain[:len(customDomain)-len(domain)-1] if _, ok := recordDict[subDomain]; !ok { createRecord(subDomain) } else { updateRecord(recordDict[subDomain], subDomain) } } } func deleteHandler(obj interface{}) { logger.Info(\u0026#34;Detect Ingress Delete Event\u0026#34;) recordDict := getRecordDict() deleteIngress := obj.(*networkingv1.Ingress) for i := 0; i \u0026lt; len(deleteIngress.Spec.Rules); i++ { customDomain := deleteIngress.Spec.Rules[i].Host // customDomain 判断是否以 domain 结尾 if customDomain[len(customDomain)-len(domain):] != domain { continue } subDomain := customDomain[:len(customDomain)-len(domain)-1] if _, ok := recordDict[subDomain]; ok { deleteRecord(recordDict[subDomain]) } } } func k8sInformer() { // 创建共享 Informer 工厂 factory := informers.NewSharedInformerFactory(clientSet, 0) // 获取 Informer Ingress watcher ingressInformer := factory.Networking().V1().Ingresses().Informer() // 添加事件处理器来监听 Ingress 资源的变化 _, err := ingressInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: addHandler, UpdateFunc: updateHandler, DeleteFunc: deleteHandler, }) if err != nil { return } // 启动 informer 并等待缓存同步 stopCh := make(chan struct{}) defer close(stopCh) factory.Start(stopCh) factory.WaitForCacheSync(stopCh) // 阻塞，持续监听事件 \u0026lt;-stopCh } func main() { // 初始化 initLogger() initCheck() // 创建 DNSPod 客户端 credential := common.NewCredential(secretId, secretKey) cpf := profile.NewClientProfile() cpf.HttpProfile.Endpoint = \u0026#34;dnspod.tencentcloudapi.com\u0026#34; dnsPodClient, _ = dnspod.NewClient(credential, \u0026#34;\u0026#34;, cpf) // 创建 Kubernetes 客户端 var config *rest.Config var err error // 判断是否在集群内部 if os.Getenv(\u0026#34;KUBERNETES_SERVICE_HOST\u0026#34;) != \u0026#34;\u0026#34; { config, err = rest.InClusterConfig() if err != nil { logger.Panic(err.Error()) } } else { if _, err = os.Stat(\u0026#34;./.kube/config\u0026#34;); os.IsNotExist(err) { logger.Panic(\u0026#34;Please set the kubeconfig file\u0026#34;) } config, err = clientcmd.BuildConfigFromFlags(\u0026#34;\u0026#34;, \u0026#34;./.kube/config\u0026#34;) } clientSet, err = kubernetes.NewForConfig(config) if err != nil { logger.Panic(err.Error()) } k8sInformer() } Dockerfile\n1 2 3 4 5 6 7 8 9 10 FROM golang:alpine AS builder WORKDIR /app COPY . . RUN go mod download RUN go build -o main . FROM alpine:latest WORKDIR /app COPY --from=builder /app/main . CMD [\u0026#34;./main\u0026#34;] 构建镜像\n1 docker build -t harbor.example.com/devops/ingress-dnspod-solver:latest . 推送镜像\n1 docker push harbor.example.com/devops/ingress-dnspod-solver:latest ","date":"2024-12-17T00:00:00Z","permalink":"/p/k8s_ingress_domain_auto_resolution/","title":"Kubernetes Ingress域名自动解析与证书管理"},{"content":"\r使用 Ingress | Kubernetes 作为 K8s 的应用流量转发代理，并配置证书\n服务部署\r由于使用的 K8S 集群并非云服务商提供的，需要使用 ingress-nginx 的 LoadBalancer 类型服务的话，先部署 metallb，MetalLB 是裸机 Kubernetes 集群的负载均衡器实现，使用标准路由协议。\n部署 MetalLB\r1 2 3 4 5 6 7 8 9 # see what changes would be made, returns nonzero returncode if different kubectl get configmap kube-proxy -n kube-system -o yaml | \\ sed -e \u0026#34;s/strictARP: false/strictARP: true/\u0026#34; | \\ kubectl diff -f - -n kube-system # actually apply the changes, returns nonzero returncode on errors only kubectl get configmap kube-proxy -n kube-system -o yaml | \\ sed -e \u0026#34;s/strictARP: false/strictARP: true/\u0026#34; | \\ kubectl apply -f - -n kube-system 1 kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml ip-pool.yaml 创建 IP 池：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: default namespace: metallb-system spec: addresses: - 172.16.0.90/32 # 由于是裸机集群，需要手动指定 IP 地址 autoAssign: true --- apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: default namespace: metallb-system spec: ipAddressPools: - default 1 kubectl apply -f ip-pool.yaml 部署 ingress-nginx\r1 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml 配置证书\r下面介绍 3 种方式配置证书：分别是手动申请自签名证书、手动申请受信任证书、使用 cert-manager 申请证书\n手动申请自签名证书\r申请证书，可以使用 OpenSSL，也可以使用其他工具 tls.crt 证书文件、tls.key 私钥文件，base64 编码后的内容，可以定制证书的相关信息：国家、省、市、公司、部门、域名、有效时间等\n1 2 3 4 5 6 7 8 9 10 11 # 生成私钥 openssl genrsa -out tls.key 2048 # 生成私钥文件 tls.key # 生成证书请求 openssl req -new -key tls.key -out tls.csr -subj \u0026#34;/C=\u0026lt;country\u0026gt;/ST=\u0026lt;province\u0026gt;/L=\u0026lt;city\u0026gt;/O=\u0026lt;company\u0026gt;/OU=\u0026lt;department\u0026gt;/CN=\u0026lt;domain\u0026gt;\u0026#34; # 生成证书请求文件 tls.csr # 生成证书 openssl x509 -req -in tls.csr -signkey tls.key -out tls.crt -days 3650 # 生成证书文件 tls.crt # 查看证书信息 openssl x509 -in tls.crt -text -noout test-ingress.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 apiVersion: v1 kind: Namespace metadata: name: test-ingress --- apiVersion: apps/v1 kind: Deployment metadata: name: service1 namespace: test-ingress spec: replicas: 1 selector: matchLabels: app: service1 template: metadata: labels: app: service1 spec: containers: - name: service1 image: nginx:alpine --- apiVersion: v1 kind: Service metadata: name: service1 namespace: test-ingress spec: selector: app: service1 ports: - name: http protocol: TCP port: 80 targetPort: 80 # letsencrypt 证书，手动创建 --- apiVersion: v1 kind: Secret metadata: name: \u0026lt;domain\u0026gt; namespace: test-ingress data: tls.crt: \u0026lt;base64 encoded cert\u0026gt; tls.key: \u0026lt;base64 encoded key\u0026gt; type: kubernetes.io/tls --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress namespace: test-ingress spec: ingressClassName: nginx tls: - hosts: - \u0026lt;domain\u0026gt; secretName: \u0026lt;secret name\u0026gt; rules: - host: \u0026lt;domain\u0026gt; http: paths: - path: / pathType: Prefix backend: service: name: service1 port: number: 80 1 kubectl apply -f test-ingress.yaml 手动申请受信任证书\r申请证书，可以使用 Let\u0026rsquo;s Encrypt，也可以使用其他证书颁发机构 tls.crt 证书文件、tls.key 私钥文件，base64 编码后的内容，默认有效时间 90 天\ntest-ingress.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 apiVersion: v1 kind: Namespace metadata: name: test-ingress --- apiVersion: apps/v1 kind: Deployment metadata: name: service1 namespace: test-ingress spec: replicas: 1 selector: matchLabels: app: service1 template: metadata: labels: app: service1 spec: containers: - name: service1 image: nginx:alpine --- apiVersion: v1 kind: Service metadata: name: service1 namespace: test-ingress spec: selector: app: service1 ports: - name: http protocol: TCP port: 80 targetPort: 80 # letsencrypt 证书，手动创建 --- apiVersion: v1 kind: Secret metadata: name: \u0026lt;domain\u0026gt; namespace: test-ingress data: tls.crt: \u0026lt;base64 encoded cert\u0026gt; tls.key: \u0026lt;base64 encoded key\u0026gt; type: kubernetes.io/tls --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress namespace: test-ingress spec: ingressClassName: nginx tls: - hosts: - \u0026lt;domain\u0026gt; secretName: \u0026lt;secret name\u0026gt; rules: - host: \u0026lt;domain\u0026gt; http: paths: - path: / pathType: Prefix backend: service: name: service1 port: number: 80 1 kubectl apply -f test-ingress.yaml 使用 cert-manager 申请证书\rcert-manager 是一个 Kubernetes 证书管理控制器，基于 Kubernetes 的 CustomResourceDefinitions 资源，提供了证书申请、颁发、更新、删除等功能 下面介绍如何使用 cert-manager 申请证书，三种方式：SelfSigned Issuer 类型证书、ACME Issuer 类型证书、CA Issuer 类型证书\n安装 cert-manager\r1 kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.yaml 默认情况下，cert-manager 将安装到 cert-manager 命名空间中。可以在不同的命名空间中运行证书管理器，尽管需要对部署清单进行修改。\n安装证书管理器后，可以通过检查运行Pod的证书管理器命名空间来验证它是否正确部署：\n1 2 3 4 5 6 $ kubectl get pods --namespace cert-manager NAME READY STATUS RESTARTS AGE cert-manager-cainjector-5fd6444f95-kmbmd 1/1 Running 0 60m cert-manager-d894bbbd4-lrwp5 1/1 Running 0 60m cert-manager-webhook-869674f96f-ljffr 1/1 Running 0 60m 配置 SelfSigned Issuer 类型证书\rtest-ingress.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 apiVersion: v1 kind: Namespace metadata: name: test-ingress --- apiVersion: apps/v1 kind: Deployment metadata: name: service1 namespace: test-ingress spec: replicas: 1 selector: matchLabels: app: service1 template: metadata: labels: app: service1 spec: containers: - name: service1 image: nginx:alpine --- apiVersion: v1 kind: Service metadata: name: service1 namespace: test-ingress spec: selector: app: service1 ports: - name: http protocol: TCP port: 80 targetPort: 80 # SelfSigned 证书 100 年，自动创建 --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: selfsigned-issuer namespace: test-ingress spec: selfSigned: {} # SelfSigned 证书 100 年，自动创建 --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: \u0026lt;domain\u0026gt; namespace: test-ingress spec: secretName: \u0026lt;secret name\u0026gt; duration: 876000h # 100 years renewBefore: 720h # 在证书过期前 30 天自动续签 issuerRef: name: selfsigned-issuer kind: ClusterIssuer commonName: \u0026lt;domain\u0026gt; subject: organizations: - \u0026lt;company\u0026gt; organizationalUnits: - \u0026lt;department\u0026gt; isCA: true privateKey: algorithm: RSA encoding: PKCS1 size: 2048 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress namespace: test-ingress annotations: cert-manager.io/cluster-issuer: selfsigned-issuer spec: ingressClassName: nginx tls: - hosts: - \u0026lt;domain\u0026gt; secretName: \u0026lt;secret name\u0026gt; rules: - host: \u0026lt;domain\u0026gt; http: paths: - path: / pathType: Prefix backend: service: name: service1 port: number: 80 1 kubectl apply -f test-ingress.yaml ACME（Automated Certificate Management Environment）是一种协议，用于自动化证书颁发和更新。ACME协议由IETF标准化，目前最常见的实现是Let\u0026rsquo;s Encrypt。ACME协议的一个关键特性是，它允许证书颁发机构（CA）在不需要人工干预的情况下验证证书请求者的身份。ACME 协议的另一个关键特性是，它允许证书颁发机构自动更新证书，而无需人工干预。 颁发者类型表示在自动证书管理环境（ACME）证书颁发机构服务器上注册的单个帐户。创建新的ACME颁发者时，证书管理器将生成一个私钥，用于在ACME服务器上识别。 默认情况下，公共ACME服务器颁发的证书通常由客户端的计算机信任。这意味着，例如，访问由为该URL颁发的ACME证书支持的网站，默认情况下会被大多数客户端的web浏览器信任。ACME证书通常是免费的。\n配置 ACME Issuer http01 类型证书\rHTTP01 质询是通过展示一个计算出的密钥来完成的，这个密钥需要出现在一个可通过互联网访问的 HTTP URL 端点上。这个 URL 将使用申请证书的域名。一旦 ACME 服务器能够通过互联网从这个 URL 获取到这个密钥，就能验证你是该域名的拥有者。 当创建 HTTP01 质询时，cert-manager 会自动配置你的集群入口（ingress），将访问这个 URL 的流量路由到一个小型的网络服务器上，由它展示该密钥。 通俗的说，就是 cert-manager 会自动创建 ingress 路由到一个小型的网络服务器上，由它展示该密钥，以验证你是该域名的拥有者。\nfake 证书一年，自动创建：kubernetes ingress controller fake cert\ntest-ingress.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 apiVersion: v1 kind: Namespace metadata: name: test-ingress --- apiVersion: apps/v1 kind: Deployment metadata: name: service1 namespace: test-ingress spec: replicas: 1 selector: matchLabels: app: service1 template: metadata: labels: app: service1 spec: containers: - name: service1 image: nginx:alpine --- apiVersion: v1 kind: Service metadata: name: service1 namespace: test-ingress spec: selector: app: service1 ports: - name: http protocol: TCP port: 80 targetPort: 80 # ACME 证书，自动创建 --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: sencrypt-prod namespace: test-ingress spec: acme: email: CoderKang@hotmail.com privateKeySecretRef: name: sencrypt-prod server: https://acme-v02.api.letsencrypt.org/directory solvers: - http01: ingress: class: nginx --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress namespace: test-ingress annotations: cert-manager.io/cluster-issuer: sencrypt-prod spec: ingressClassName: nginx tls: - hosts: - test.example.com secretName: example-com rules: - host: test.example.com http: paths: - path: / pathType: Prefix backend: service: name: service1 port: number: 80 1 kubectl apply -f test-ingress.yaml 配置 ACME Issuer dns01 类型证书\rDNS01 质询是通过提供一个存在于 DNS TXT 记录中的计算密钥来完成的。一旦这个 TXT 记录在互联网中传播开，ACME 服务器就能通过 DNS 查询成功获取该密钥，并验证申请证书的客户端是该域名的拥有者。 如果拥有正确的权限，cert-manager 会自动在你指定的 DNS 服务提供商处添加这个 TXT 记录。\n但是此次使用的是腾讯云 DNS 服务商，cert-manager 默认不支持腾讯云 DNS 服务商，需要自定义解析器 cert-manager webhook\nDNS01 - cert-manager Documentation\n1 kubectl apply -f https://raw.githubusercontent.com/imroc/cert-manager-webhook-dnspod/master/bundle.yaml test-ingress.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 apiVersion: v1 kind: Namespace metadata: name: test-ingress --- apiVersion: apps/v1 kind: Deployment metadata: name: service1 namespace: test-ingress spec: replicas: 1 selector: matchLabels: app: service1 template: metadata: labels: app: service1 spec: containers: - name: service1 image: nginx:alpine --- apiVersion: v1 kind: Service metadata: name: service1 namespace: test-ingress spec: selector: app: service1 ports: - name: http protocol: TCP port: 80 targetPort: 80 # letsencrypt 自动创建，有效期 90 天 # 配置腾讯云 DNS 服务商的 SecretId 和 SecretKey # https://console.dnspod.cn/account/token/apikey --- apiVersion: v1 stringData: secret-key: \u0026lt;tencent cloud secret key\u0026gt; # 腾讯云 SecretKey kind: Secret metadata: name: dnspod-secret namespace: cert-manager type: Opaque --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: sencrypt-prod namespace: test-ingress spec: acme: email: CoderKang@hotmail.com preferredChain: \u0026#34;\u0026#34; privateKeySecretRef: name: dnspod-letsencrypt server: https://acme-v02.api.letsencrypt.org/directory solvers: - dns01: webhook: config: secretId: \u0026lt;tencent cloud secret id\u0026gt; # 腾讯云 SecretId secretKeyRef: key: secret-key name: dnspod-secret ttl: 600 groupName: acme.imroc.cc solverName: dnspod --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: example-com namespace: test-ingress spec: dnsNames: - test.example.com issuerRef: kind: ClusterIssuer name: sencrypt-prod secretName: example-com --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress namespace: test-ingress annotations: cert-manager.io/cluster-issuer: sencrypt-prod spec: ingressClassName: nginx tls: - hosts: - test.example.com secretName: example-com rules: - host: test.example.com http: paths: - path: / pathType: Prefix backend: service: name: service1 port: number: 80 1 kubectl apply -f test-ingress.yaml ","date":"2024-12-16T00:00:00Z","permalink":"/p/k8s_ingress_nginx_certificate_configuration/","title":"Kubernetes ingress-nginx 服务证书配置指南"},{"content":"ingress\r需要搭建 ingress（详见13-K8s ingress-nginx 服务证书）并开启 TCP 转发（详见23-K8s ingress TCP 转发）。\njumpserver\r\u0026ldquo;安装\u0026rdquo;\n1 helm install jms-k8s ./jumpserver-v4.6.0 -n jumpserver --create-namespace -f values.yaml \u0026ldquo;更新\u0026rdquo;\n1 helm upgrade jms-k8s ./jumpserver-v4.6.0 -n jumpserver --create-namespace -f values.yaml \u0026ldquo;卸载\u0026rdquo;\n1 helm -n jumpserver delete jms-k8s core 组件镜像\r修改 core 组件，支持钉钉告警\n1 2 3 4 docker pull docker.1ms.run/jumpserver/core:v4.6.0-ce cd core docker build -t jumpserver/core:v4.6.1-ce . \u0026ldquo;Dockerfile\u0026rdquo;\n1 2 3 FROM docker.1ms.run/jumpserver/core:v4.6.0-ce COPY ./notifications.py /opt/jumpserver/apps/notifications/notifications.py \u0026ldquo;notifications.py\u0026rdquo;\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 import textwrap import time import traceback from itertools import chain from celery import shared_task from django.utils.translation import gettext_lazy as _ from html2text import HTML2Text from common.utils import lazyproperty from common.utils.timezone import local_now from notifications.backends import BACKEND from settings.utils import get_login_title from terminal.const import RiskLevelChoices from users.models import User from .models import SystemMsgSubscription, UserMsgSubscription __all__ = (\u0026#39;SystemMessage\u0026#39;, \u0026#39;UserMessage\u0026#39;, \u0026#39;system_msgs\u0026#39;, \u0026#39;Message\u0026#39;) system_msgs = [] user_msgs = [] class MessageType(type): def __new__(cls, name, bases, attrs: dict): clz = type.__new__(cls, name, bases, attrs) if \u0026#39;message_type_label\u0026#39; in attrs \\ and \u0026#39;category\u0026#39; in attrs \\ and \u0026#39;category_label\u0026#39; in attrs: message_type = clz.get_message_type() msg = { \u0026#39;message_type\u0026#39;: message_type, \u0026#39;message_type_label\u0026#39;: attrs[\u0026#39;message_type_label\u0026#39;], \u0026#39;category\u0026#39;: attrs[\u0026#39;category\u0026#39;], \u0026#39;category_label\u0026#39;: attrs[\u0026#39;category_label\u0026#39;], } if issubclass(clz, SystemMessage): system_msgs.append(msg) elif issubclass(clz, UserMessage): user_msgs.append(msg) return clz @shared_task( verbose_name=_(\u0026#39;Publish the station message\u0026#39;), description=_( \u0026#34;\u0026#34;\u0026#34;This task needs to be executed for sending internal messages for system alerts, work orders, and other notifications\u0026#34;\u0026#34;\u0026#34; ) ) def publish_task(receive_user_ids, backends_msg_mapper): Message.send_msg(receive_user_ids, backends_msg_mapper) def send_dingtalk_task(message): # 发送到自定义的 Webhook import hmac, hashlib, base64, urllib.parse timestamp = str(round(time.time() * 1000)) # JumpServer access_token = \u0026#39;\u0026lt;access token\u0026gt;\u0026#39; secret = \u0026#39;\u0026lt;secret\u0026gt;\u0026#39; # smartvision secret_enc = secret.encode(\u0026#39;utf-8\u0026#39;) string_to_sign = \u0026#39;{}\\n{}\u0026#39;.format(timestamp, secret) string_to_sign_enc = string_to_sign.encode(\u0026#39;utf-8\u0026#39;) hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest() sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) form_data = { \u0026#34;msgtype\u0026#34;: \u0026#34;markdown\u0026#34;, \u0026#34;markdown\u0026#34;: { \u0026#34;title\u0026#34;: \u0026#34;JumpServer 监控报警\u0026#34;, \u0026#34;text\u0026#34;: message }, \u0026#34;at\u0026#34;: { \u0026#34;atMobiles\u0026#34;: [ \u0026#34;\u0026lt;mobile\u0026gt;\u0026#34; ], \u0026#34;isAtAll\u0026#34;: False } } import requests try: res = requests.post( url=f\u0026#39;https://oapi.dingtalk.com/robot/send?access_token={access_token}\u0026amp;timestamp={timestamp}\u0026amp;sign={sign}\u0026#39;, json=form_data) except Exception as e: with open(\u0026#39;/opt/error.log\u0026#39;, \u0026#39;a+\u0026#39;) as f: f.write(str(e)) class Message(metaclass=MessageType): \u0026#34;\u0026#34;\u0026#34; 这里封装了什么？ 封装不同消息的模板，提供统一的发送消息的接口 - publish 该方法的实现与消息订阅的表结构有关 - send_msg \u0026#34;\u0026#34;\u0026#34; message_type_label: str category: str category_label: str text_msg_ignore_links = True command = None @classmethod def get_message_type(cls): return cls.__name__ def publish_async(self): self.publish(is_async=True) @classmethod def gen_test_msg(cls): raise NotImplementedError def publish(self, is_async=False): raise NotImplementedError def get_backend_msg_mapper(self, backends): backends = set(backends) backends.add(BACKEND.SITE_MSG) # 站内信必须发 backends_msg_mapper = {} for backend in backends: backend = BACKEND(backend) if not backend.is_enable: continue get_msg_method = getattr(self, f\u0026#39;get_{backend}_msg\u0026#39;, self.get_common_msg) msg = get_msg_method() backends_msg_mapper[backend] = msg return backends_msg_mapper @staticmethod def send_msg(receive_user_ids, backends_msg_mapper): for backend, msg in backends_msg_mapper.items(): try: backend = BACKEND(backend) client = backend.client() users = User.objects.filter(id__in=receive_user_ids).all() client.send_msg(users, **msg) except NotImplementedError: continue except: traceback.print_exc() @classmethod def send_test_msg(cls, ding=True, wecom=False): msg = cls.gen_test_msg() if not msg: return from users.models import User users = User.objects.filter(username=\u0026#39;admin\u0026#39;) backends = [] if ding: backends.append(BACKEND.DINGTALK) if wecom: backends.append(BACKEND.WECOM) msg.send_msg(users, backends) @staticmethod def get_common_msg() -\u0026gt; dict: return {\u0026#39;subject\u0026#39;: \u0026#39;\u0026#39;, \u0026#39;message\u0026#39;: \u0026#39;\u0026#39;} def get_html_msg(self) -\u0026gt; dict: return self.get_common_msg() @staticmethod def html_to_markdown(html_msg): h = HTML2Text() h.body_width = 0 content = html_msg[\u0026#39;message\u0026#39;] html_msg[\u0026#39;message\u0026#39;] = h.handle(content) return html_msg def get_markdown_msg(self) -\u0026gt; dict: return self.html_to_markdown(self.get_html_msg()) def get_text_msg(self) -\u0026gt; dict: h = HTML2Text() h.body_width = 90 msg = self.get_html_msg() content = msg[\u0026#39;message\u0026#39;] h.ignore_links = self.text_msg_ignore_links msg[\u0026#39;message\u0026#39;] = h.handle(content) return msg @lazyproperty def common_msg(self) -\u0026gt; dict: return self.get_common_msg() @lazyproperty def text_msg(self) -\u0026gt; dict: msg = self.get_text_msg() return msg @lazyproperty def markdown_msg(self): return self.get_markdown_msg() @lazyproperty def get_jumpserver_dingtalk_msg(self) -\u0026gt; str: date_str = time.strftime(\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;, time.localtime()) msg = f\u0026#34;\u0026#34;\u0026#34;## \u0026lt;font color=\u0026#34;#FF0000\u0026#34;\u0026gt;[JumpServer 监控告警](https://jumpserver.example.com/)\u0026lt;/font\u0026gt;🔥 ### \u0026lt;font color=\u0026#34;#FF0000\u0026#34;\u0026gt;告警状态\u0026lt;/font\u0026gt;：{RiskLevelChoices.get_label(self.command[\u0026#39;risk_level\u0026#39;])} ### \u0026lt;font color=\u0026#34;#FF0000\u0026#34;\u0026gt;告警主机\u0026lt;/font\u0026gt;：{self.command[\u0026#39;asset\u0026#39;]} ### \u0026lt;font color=\u0026#34;#FF0000\u0026#34;\u0026gt;目标用户\u0026lt;/font\u0026gt;：{self.command[\u0026#39;user\u0026#39;]} ### \u0026lt;font color=\u0026#34;#FF0000\u0026#34;\u0026gt;输入命令\u0026lt;/font\u0026gt;：`{self.command[\u0026#39;input\u0026#39;]}` ### \u0026lt;font color=\u0026#34;#FF0000\u0026#34;\u0026gt;告警详情\u0026lt;/font\u0026gt;：用户 {self.command[\u0026#39;user\u0026#39;]} 在 {self.command[\u0026#39;asset\u0026#39;]} 上执行了危险命令 `{self.command[\u0026#39;input\u0026#39;]}`，请及时处理！ ### \u0026lt;font color=\u0026#34;#FF0000\u0026#34;\u0026gt;触发时间\u0026lt;/font\u0026gt;：{date_str}\u0026#34;\u0026#34;\u0026#34; return msg @lazyproperty def html_msg(self) -\u0026gt; dict: msg = self.get_html_msg() return msg @lazyproperty def html_msg_with_sign(self): msg = self.get_html_msg() msg[\u0026#39;message\u0026#39;] = textwrap.dedent(\u0026#34;\u0026#34;\u0026#34; {} \u0026lt;small\u0026gt; \u0026lt;br /\u0026gt; — \u0026lt;br /\u0026gt; {} \u0026lt;/small\u0026gt; \u0026#34;\u0026#34;\u0026#34;).format(msg[\u0026#39;message\u0026#39;], self.signature) return msg @lazyproperty def text_msg_with_sign(self): msg = self.get_text_msg() msg[\u0026#39;message\u0026#39;] = textwrap.dedent(\u0026#34;\u0026#34;\u0026#34; {} — {} \u0026#34;\u0026#34;\u0026#34;).format(msg[\u0026#39;message\u0026#39;], self.signature) return msg @lazyproperty def signature(self): return get_login_title() # -------------------------------------------------------------- # 支持不同发送消息的方式定义自己的消息内容，比如有些支持 html 标签 def get_dingtalk_msg(self) -\u0026gt; dict: # 钉钉相同的消息一天只能发一次，所以给所有消息添加基于时间的序号，使他们不相同 message = self.markdown_msg[\u0026#39;message\u0026#39;] time = local_now().strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;) suffix = \u0026#39;\\n{}: {}\u0026#39;.format(_(\u0026#39;Time\u0026#39;), time) return { \u0026#39;subject\u0026#39;: self.markdown_msg[\u0026#39;subject\u0026#39;], \u0026#39;message\u0026#39;: message + suffix } def get_wecom_msg(self) -\u0026gt; dict: return self.markdown_msg def get_feishu_msg(self) -\u0026gt; dict: return self.markdown_msg def get_lark_msg(self) -\u0026gt; dict: return self.markdown_msg def get_email_msg(self) -\u0026gt; dict: return self.html_msg_with_sign def get_site_msg_msg(self) -\u0026gt; dict: return self.html_msg def get_slack_msg(self) -\u0026gt; dict: return self.markdown_msg def get_sms_msg(self) -\u0026gt; dict: return self.text_msg_with_sign @classmethod def get_all_sub_messages(cls): def get_subclasses(cls): \u0026#34;\u0026#34;\u0026#34;returns all subclasses of argument, cls\u0026#34;\u0026#34;\u0026#34; if issubclass(cls, type): subclasses = cls.__subclasses__(cls) else: subclasses = cls.__subclasses__() for subclass in subclasses: subclasses.extend(get_subclasses(subclass)) return subclasses messages_cls = get_subclasses(cls) return messages_cls @classmethod def test_all_messages(cls, ding=True, wecom=False): messages_cls = cls.get_all_sub_messages() for _cls in messages_cls: try: _cls.send_test_msg(ding=ding, wecom=wecom) except NotImplementedError: continue class SystemMessage(Message): def publish(self, is_async=False): subscription = SystemMsgSubscription.objects.get( message_type=self.get_message_type() ) # 只发送当前有效后端 receive_backends = subscription.receive_backends receive_backends = BACKEND.filter_enable_backends(receive_backends) users = [ *subscription.users.all(), *chain(*[g.users.all() for g in subscription.groups.all()]) ] receive_user_ids = [u.id for u in users] backends_msg_mapper = self.get_backend_msg_mapper(receive_backends) if is_async: send_dingtalk_task(self.get_jumpserver_dingtalk_msg) # publish_task.delay(receive_user_ids, backends_msg_mapper) else: self.send_msg(receive_user_ids, backends_msg_mapper) @classmethod def post_insert_to_db(cls, subscription: SystemMsgSubscription): pass @classmethod def gen_test_msg(cls): raise NotImplementedError class UserMessage(Message): user: User def __init__(self, user): self.user = user def publish(self, is_async=False): \u0026#34;\u0026#34;\u0026#34; 发送消息到每个用户配置的接收方式上 \u0026#34;\u0026#34;\u0026#34; sub = UserMsgSubscription.objects.get(user=self.user) backends_msg_mapper = self.get_backend_msg_mapper(sub.receive_backends) receive_user_ids = [self.user.id] if is_async: send_dingtalk_task(self.get_jumpserver_dingtalk_msg) # publish_task.delay(receive_user_ids, backends_msg_mapper) else: self.send_msg(receive_user_ids, backends_msg_mapper) @classmethod def get_test_user(cls): from users.models import User return User.objects.all().first() @classmethod def gen_test_msg(cls): raise NotImplementedError values.yaml\r```yaml\r# Default values for jumpserver.\r# This is a YAML-formatted file.\r# Declare variables to be passed into your templates.\rnameOverride: \u0026quot;\u0026quot;\rfullnameOverride: \u0026quot;\u0026quot;\r## @param global.imageRegistry Global Docker image registry\r## @param global.imagePullSecrets Global Docker registry secret names as an array\r## @param global.storageClass Global StorageClass for Persistent Volume(s)\r## @param global.redis.password Global Redis™ password (overrides `externalRedis.password`)\r##\rglobal:\rimageRegistry: docker.1ms.run\rimageOwner: jumpserver\r## E.g.\r# imagePullSecrets:\r# - myRegistryKeySecretName\r##\rimagePullSecrets: []\rstorageClass: \u0026quot;\u0026quot;\r## Please configure your PostgreSQL server first\r## Jumpserver will not start the external PostgreSQL server.\r##\rexternalDatabase:\rengine: postgresql\rhost: postgresql-svc.middleware.svc.cluster.local\rport: 5432\ruser: postgres\rpassword: \u0026quot;uLwHDyYr\u0026quot;\rdatabase: jumpserver\r## Please configure your Redis server first\r## Jumpserver will not start the external Redis server.\r##\rexternalSentinel:\r{}\r# hosts: mymaster/localhost:26379,localhost:26380,localhost:26381\r# password: \u0026quot;\u0026quot;\r# socketTimeout: 5\r## Sentinel or Redis one of them must be configured.\rexternalRedis:\rhost: redis-svc.middleware.svc.cluster.local\rport: 6379\rpassword: \u0026quot;******\u0026quot;\rserviceAccount:\r## Specifies whether a service account should be created\rcreate: false\r## The name of the service account to use.\r## If not set and create is true, a name is generated using the fullname template\rname:\ringress:\renabled: true\rannotations:\rcert-manager.io/cluster-issuer: sencrypt-prod\r# nginx.ingress.kubernetes.io/enable-cors: \u0026quot;true\u0026quot;\r# nginx.ingress.kubernetes.io/cors-allow-methods: \u0026quot;GET, POST, OPTIONS, PUT, DELETE\u0026quot;\r# nginx.ingress.kubernetes.io/cors-allow-headers: \u0026quot;Authorization, Content-Type\u0026quot;\r# nginx.ingress.kubernetes.io/cors-allow-credentials: \u0026quot;true\u0026quot;\r# nginx.ingress.kubernetes.io/cors-allow-origin: \u0026quot;*\u0026quot;\r# kubernetes.io/tls-acme: \u0026quot;true\u0026quot;\rkubernetes.io/ingress.class: nginx\rnginx.ingress.kubernetes.io/proxy-body-size: \u0026quot;4096m\u0026quot;\rnginx.ingress.kubernetes.io/server-snippets: |\rproxy_set_header Upgrade \u0026quot;websocket\u0026quot;;\rproxy_set_header Connection \u0026quot;Upgrade\u0026quot;;\rhosts:\r- \u0026quot;jumpserver.example.com\u0026quot;\rtls:\r- hosts:\r- jumpserver.example.com\rsecretName: jumpserver.example.com\rcore:\renabled: true\rlabels:\rapp.jumpserver.org/name: jms-core\rconfig:\r## Generate a new random secret key by execute `cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 50`\rsecretKey: \u0026quot;TAcm23tDmN9n7cVCbgkDo6ln62qTuXYDKfLFYPFLd1y7DL5cS9\u0026quot;\r## Generate a new random bootstrap token by execute `cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 24`\rbootstrapToken: \u0026quot;KX8k37hBbQjmKgRDR17QF7EB\u0026quot;\r## Enabled it for debug\rdebug: false\rlog:\rlevel: ERROR\rreplicaCount: 1\rimage:\rregistry: docker.1ms.run\rpullPolicy: IfNotPresent\renv:\r## See: https://docs.jumpserver.org/zh/master/admin-guide/env/#core\rSESSION_EXPIRE_AT_BROWSER_CLOSE: true\r# SESSION_COOKIE_AGE: 86400\r# SECURITY_VIEW_AUTH_NEED_MFA: true\r## Django CSRF_TRUSTED_ORIGINS need to be set to the domain name of the jumpserver (https://docs.jumpserver.org/zh/v3/installation/upgrade_notice/)\rDOMAINS: \u0026quot;192.168.142.56:33380\u0026quot;\rlivenessProbe:\rinitialDelaySeconds: 90\rfailureThreshold: 3\rtimeoutSeconds: 5\rhttpGet:\rpath: /api/health/\rport: web\rpodSecurityContext:\r{}\r# fsGroup: 2000\rsecurityContext:\r{}\r# capabilities:\r# drop:\r# - ALL\r# readOnlyRootFilesystem: true\r# runAsNonRoot: true\r# runAsUser: 1000\rservice:\rtype: ClusterIP\rweb:\rport: 8080\rresources:\r{}\r## We usually recommend not to specify default resources and to leave this as a conscious\r## choice for the user. This also increases chances charts run on environments with little\r## resources, such as Minikube. If you do want to specify resources, uncomment the following\r## lines, adjust them as necessary, and remove the curly braces after 'resources:'.\r# limits:\r# cpu: 1000m\r# memory: 2048Mi\r# requests:\r# cpu: 500m\r# memory: 1024Mi\rpersistence:\rstorageClassName: storage\raccessModes:\r- ReadWriteMany\rsize: 100Gi\rannotations:\r\u0026quot;helm.sh/resource-policy\u0026quot;: keep\rfinalizers:\r- kubernetes.io/pvc-protection\r# subPath: \u0026quot;\u0026quot;\r# existingClaim: \u0026quot;\u0026quot;\rvolumeMounts: []\rvolumes: []\rnodeSelector: {}\rtolerations: []\raffinity: {}\rkoko:\renabled: true\rlabels:\rapp.jumpserver.org/name: jms-koko\rconfig:\rlog:\rlevel: ERROR\rreplicaCount: 1\rimage:\rregistry: docker.1ms.run\rpullPolicy: IfNotPresent\renv:\r[]\r## See: https://docs.jumpserver.org/zh/master/admin-guide/env/#koko\r# LANGUAGE_CODE: zh\r# REUSE_CONNECTION: true\r# ENABLE_LOCAL_PORT_FORWARD: true\r# ENABLE_VSCODE_SUPPORT: true\rlivenessProbe:\rinitialDelaySeconds: 10\rfailureThreshold: 3\rtimeoutSeconds: 5\rhttpGet:\rpath: /koko/health/\rport: web\rpodSecurityContext:\r{}\r# fsGroup: 2000\rsecurityContext:\rprivileged: true\r# capabilities:\r# drop:\r# - ALL\r# readOnlyRootFilesystem: true\r# runAsNonRoot: true\r# runAsUser: 1000\rservice:\rtype: ClusterIP\rweb:\rport: 5000\rssh:\rport: 2222\rresources:\r{}\r## We usually recommend not to specify default resources and to leave this as a conscious\r## choice for the user. This also increases chances charts run on environments with little\r## resources, such as Minikube. If you do want to specify resources, uncomment the following\r## lines, adjust them as necessary, and remove the curly braces after 'resources:'.\r# limits:\r# cpu: 100m\r# memory: 128Mi\r# requests:\r# cpu: 100m\r# memory: 128Mi\rpersistence:\rstorageClassName: storage\raccessModes:\r- ReadWriteMany\rsize: 10Gi\rannotations:\r\u0026quot;helm.sh/resource-policy\u0026quot;: keep\rfinalizers:\r- kubernetes.io/pvc-protection\r# subPath: \u0026quot;\u0026quot;\r# existingClaim: \u0026quot;\u0026quot;\rvolumeMounts: []\rvolumes: []\rnodeSelector: {}\rtolerations: []\raffinity: {}\rlion:\renabled: true\rlabels:\rapp.jumpserver.org/name: jms-lion\rconfig:\rlog:\rlevel: ERROR\rreplicaCount: 1\rimage:\rregistry: docker.1ms.run\rpullPolicy: IfNotPresent\renv:\r## See: https://docs.jumpserver.org/zh/master/admin-guide/env/#lion\rJUMPSERVER_ENABLE_FONT_SMOOTHING: true\r# JUMPSERVER_COLOR_DEPTH: 32\r# JUMPSERVER_ENABLE_WALLPAPER: true\r# JUMPSERVER_ENABLE_THEMING: true\r# JUMPSERVER_ENABLE_FULL_WINDOW_DRAG: true\r# JUMPSERVER_ENABLE_DESKTOP_COMPOSITION: true\r# JUMPSERVER_ENABLE_MENU_ANIMATIONS: true\rlivenessProbe:\rinitialDelaySeconds: 90\rfailureThreshold: 3\rtimeoutSeconds: 5\rhttpGet:\rpath: /lion/health/\rport: web\rpodSecurityContext:\r{}\r# fsGroup: 2000\rsecurityContext:\r{}\r# capabilities:\r# drop:\r# - ALL\r# readOnlyRootFilesystem: true\r# runAsNonRoot: true\r# runAsUser: 1000\rservice:\rtype: ClusterIP\rweb:\rport: 8081\rresources:\r{}\r## We usually recommend not to specify default resources and to leave this as a conscious\r## choice for the user. This also increases chances charts run on environments with little\r## resources, such as Minikube. If you do want to specify resources, uncomment the following\r## lines, adjust them as necessary, and remove the curly braces after 'resources:'.\r# limits:\r# cpu: 100m\r# memory: 512Mi\r# requests:\r# cpu: 100m\r# memory: 512Mi\rpersistence:\rstorageClassName: storage\raccessModes:\r- ReadWriteMany\rsize: 50Gi\rannotations:\r\u0026quot;helm.sh/resource-policy\u0026quot;: keep\rfinalizers:\r- kubernetes.io/pvc-protection\r# subPath: \u0026quot;\u0026quot;\r# existingClaim: \u0026quot;\u0026quot;\rvolumeMounts: []\rvolumes: []\rnodeSelector: {}\rtolerations: []\raffinity: {}\rchen:\renabled: true\rlabels:\rapp.jumpserver.org/name: jms-chen\rconfig:\rlog:\rlevel: ERROR\rreplicaCount: 1\rimage:\rregistry: docker.1ms.run\rpullPolicy: IfNotPresent\renv: []\rlivenessProbe:\rinitialDelaySeconds: 60\rfailureThreshold: 3\rtimeoutSeconds: 5\rhttpGet:\rpath: /chen\rport: web\rpodSecurityContext:\r{}\r# fsGroup: 2000\rsecurityContext:\r{}\r# capabilities:\r# drop:\r# - ALL\r# readOnlyRootFilesystem: true\r# runAsNonRoot: true\r# runAsUser: 1000\rservice:\rtype: ClusterIP\rweb:\rport: 8082\rresources:\r{}\r## We usually recommend not to specify default resources and to leave this as a conscious\r## choice for the user. This also increases chances charts run on environments with little\r## resources, such as Minikube. If you do want to specify resources, uncomment the following\r## lines, adjust them as necessary, and remove the curly braces after 'resources:'.\r# limits:\r# cpu: 100m\r# memory: 128Mi\r# requests:\r# cpu: 100m\r# memory: 128Mi\rpersistence:\rstorageClassName: storage\raccessModes:\r- ReadWriteMany\rsize: 10Gi\rannotations:\r\u0026quot;helm.sh/resource-policy\u0026quot;: keep\rfinalizers:\r- kubernetes.io/pvc-protection\r# subPath: \u0026quot;\u0026quot;\r# existingClaim: \u0026quot;\u0026quot;\rvolumeMounts: []\rvolumes: []\rnodeSelector: {}\rtolerations: []\raffinity: {}\rxpack:\renabled: false\rmagnus:\rlabels:\rapp.jumpserver.org/name: jms-magnus\rconfig:\rlog:\rlevel: ERROR\rreplicaCount: 1\rimage:\rregistry: docker.1ms.run\rpullPolicy: IfNotPresent\renv: []\rlivenessProbe:\rinitialDelaySeconds: 10\rfailureThreshold: 3\rtimeoutSeconds: 5\rhttpGet:\rpath: /health\rport: web\rpodSecurityContext:\r{}\r# fsGroup: 2000\rsecurityContext:\r{}\r# capabilities:\r# drop:\r# - ALL\r# readOnlyRootFilesystem: true\r# runAsNonRoot: true\r# runAsUser: 1000\rservice:\rtype: ClusterIP\rweb:\rport: 8088\rmysql:\rport: 33061\rmariadb:\rport: 33062\rredis:\rport: 63790\rpostgresql:\rport: 54320\rsqlserver:\rport: 14330\roracle:\rports: 30000-30100\rresources:\r{}\r## We usually recommend not to specify default resources and to leave this as a conscious\r## choice for the user. This also increases chances charts run on environments with little\r## resources, such as Minikube. If you do want to specify resources, uncomment the following\r## lines, adjust them as necessary, and remove the curly braces after 'resources:'.\r# limits:\r# cpu: 100m\r# memory: 512Mi\r# requests:\r# cpu: 100m\r# memory: 512Mi\rpersistence:\rstorageClassName: storage\raccessModes:\r- ReadWriteMany\rsize: 10Gi\rannotations:\r\u0026quot;helm.sh/resource-policy\u0026quot;: keep\rfinalizers:\r- kubernetes.io/pvc-protection\r# subPath: \u0026quot;\u0026quot;\r# existingClaim: \u0026quot;\u0026quot;\rvolumeMounts: []\rvolumes: []\rnodeSelector: {}\rtolerations: []\raffinity: {}\rxrdp:\rlabels:\rapp.jumpserver.org/name: jms-xrdp\rconfig:\rlog:\rlevel: ERROR\rreplicaCount: 1\rimage:\rregistry: registry.fit2cloud.com\rpullPolicy: IfNotPresent\renv: []\rlivenessProbe:\rinitialDelaySeconds: 10\rfailureThreshold: 3\rtimeoutSeconds: 5\rtcpSocket:\rport: rdp\rpodSecurityContext:\r{}\r# fsGroup: 2000\rsecurityContext:\r{}\r# capabilities:\r# drop:\r# - ALL\r# readOnlyRootFilesystem: true\r# runAsNonRoot: true\r# runAsUser: 1000\rservice:\rtype: ClusterIP\rrdp:\rport: 3390\rresources:\r{}\r## We usually recommend not to specify default resources and to leave this as a conscious\r## choice for the user. This also increases chances charts run on environments with little\r## resources, such as Minikube. If you do want to specify resources, uncomment the following\r## lines, adjust them as necessary, and remove the curly braces after 'resources:'.\r# limits:\r# cpu: 100m\r# memory: 128Mi\r# requests:\r# cpu: 100m\r# memory: 128Mi\rpersistence:\rstorageClassName: storage\raccessModes:\r- ReadWriteMany\rsize: 50Gi\rannotations:\r\u0026quot;helm.sh/resource-policy\u0026quot;: keep\rfinalizers:\r- kubernetes.io/pvc-protection\r# subPath: \u0026quot;\u0026quot;\r# existingClaim: \u0026quot;\u0026quot;\rvolumeMounts: []\rvolumes: []\rnodeSelector: {}\rtolerations: []\raffinity: {}\rrazor:\rlabels:\rapp.jumpserver.org/name: jms-razor\rconfig:\rlog:\rlevel: ERROR\rreplicaCount: 1\rimage:\rregistry: registry.fit2cloud.com\rpullPolicy: IfNotPresent\renv: []\rlivenessProbe:\rinitialDelaySeconds: 10\rfailureThreshold: 3\rtimeoutSeconds: 5\rhttpGet:\rpath: /razor/health/\rport: web\rpodSecurityContext:\r{}\r# fsGroup: 2000\rsecurityContext:\r{}\r# capabilities:\r# drop:\r# - ALL\r# readOnlyRootFilesystem: true\r# runAsNonRoot: true\r# runAsUser: 1000\rservice:\rtype: ClusterIP\rweb:\rport: 8084\rrdp:\rport: 3389\rresources:\r{}\r## We usually recommend not to specify default resources and to leave this as a conscious\r## choice for the user. This also increases chances charts run on environments with little\r## resources, such as Minikube. If you do want to specify resources, uncomment the following\r## lines, adjust them as necessary, and remove the curly braces after 'resources:'.\r# limits:\r# cpu: 100m\r# memory: 128Mi\r# requests:\r# cpu: 100m\r# memory: 128Mi\rpersistence:\rstorageClassName: storage\raccessModes:\r- ReadWriteMany\rsize: 50Gi\rannotations:\r\u0026quot;helm.sh/resource-policy\u0026quot;: keep\rfinalizers:\r- kubernetes.io/pvc-protection\r# subPath: \u0026quot;\u0026quot;\r# existingClaim: \u0026quot;\u0026quot;\rvolumeMounts: []\rvolumes: []\rnodeSelector: {}\rtolerations: []\raffinity: {}\rvideo:\rlabels:\rapp.jumpserver.org/name: jms-video\rconfig:\rlog:\rlevel: ERROR\rreplicaCount: 1\rimage:\rregistry: registry.fit2cloud.com\rpullPolicy: IfNotPresent\renv: []\rlivenessProbe:\rinitialDelaySeconds: 10\rfailureThreshold: 3\rtimeoutSeconds: 5\rhttpGet:\rpath: /video-worker/health/\rport: web\rpodSecurityContext:\r{}\r# fsGroup: 2000\rsecurityContext:\r{}\r# capabilities:\r# drop:\r# - ALL\r# readOnlyRootFilesystem: true\r# runAsNonRoot: true\r# runAsUser: 1000\rservice:\rservice:\rtype: ClusterIP\rweb:\rport: 9000\rresources:\r{}\r## We usually recommend not to specify default resources and to leave this as a conscious\r## choice for the user. This also increases chances charts run on environments with little\r## resources, such as Minikube. If you do want to specify resources, uncomment the following\r## lines, adjust them as necessary, and remove the curly braces after 'resources:'.\r# limits:\r# cpu: 100m\r# memory: 128Mi\r# requests:\r# cpu: 100m\r# memory: 128Mi\rpersistence:\rstorageClassName: storage\raccessModes:\r- ReadWriteMany\rsize: 50Gi\rannotations:\r\u0026quot;helm.sh/resource-policy\u0026quot;: keep\rfinalizers:\r- kubernetes.io/pvc-protection\r# subPath: \u0026quot;\u0026quot;\r# existingClaim: \u0026quot;\u0026quot;\rvolumeMounts: []\rvolumes: []\rnodeSelector: {}\rtolerations: []\raffinity: {}\rweb:\renabled: true\rlabels:\rapp.jumpserver.org/name: jms-web\rreplicaCount: 1\rimage:\rregistry: docker.1ms.run\rpullPolicy: IfNotPresent\renv:\r# nginx client_max_body_size, default 4G\rCLIENT_MAX_BODY_SIZE: 4096m\r## See: https://github.com/jumpserver/docker-web/blob/master/init.sh#L37\r# USE_LB: 1, then nginx use 'proxy_set_header X-Forwarded-For $remote_addr'\r# USE_LB: 0, then nginx use 'proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for'\rUSE_LB: 0\rlivenessProbe:\rinitialDelaySeconds: 10\rfailureThreshold: 3\rtimeoutSeconds: 5\rhttpGet:\rpath: /api/health/\rport: web\rpodSecurityContext:\r{}\r# fsGroup: 2000\rsecurityContext:\r{}\r# capabilities:\r# drop:\r# - ALL\r# readOnlyRootFilesystem: true\r# runAsNonRoot: true\r# runAsUser: 1000\rservice:\rtype: ClusterIP\rweb:\rport: 80\rresources:\r{}\r## We usually recommend not to specify default resources and to leave this as a conscious\r## choice for the user. This also increases chances charts run on environments with little\r## resources, such as Minikube. If you do want to specify resources, uncomment the following\r## lines, adjust them as necessary, and remove the curly braces after 'resources:'.\r# limits:\r# cpu: 100m\r# memory: 128Mi\r# requests:\r# cpu: 100m\r# memory: 128Mi\rpersistence:\rstorageClassName: storage\raccessModes:\r- ReadWriteMany\rsize: 1Gi\rannotations:\r\u0026quot;helm.sh/resource-policy\u0026quot;: keep\rfinalizers:\r- kubernetes.io/pvc-protection\r# subPath: \u0026quot;\u0026quot;\r# existingClaim: \u0026quot;\u0026quot;\rvolumeMounts: []\rvolumes: []\rnodeSelector: {}\rtolerations: []\raffinity: {}\r```\r修改 core 模块\r1 2 3 kubectl -n jumpserver edit deployments.apps jms-k8s-jumpserver-jms-core # 将 image 镜像名称修改为 jumpserver/core:v4.6.1-ce ","date":"2024-11-19T00:00:00Z","permalink":"/p/k8s_jumpserver_deployment_dingtalk_alerts/","title":"Kubernetes 部署 JumpServer 与钉钉告警集成指南"},{"content":"Macvlan\r📝\r备注\rNetwork drivers overview | Docker Docs\nMacvlan network driver | Docker Docs\n一些应用程序，尤其是旧版应用程序或监视网络流量的应用程序，希望直接连接到物理网络。在这种情况下，你可以使用macvlan网络驱动程序为每个容器的虚拟网络接口分配一个MAC地址，使其看起来像是直接连接到物理网络的物理网络接口。在这种情况下，你需要在Docker主机上指定一个物理接口用于Macvlan，并设置网络的子网和网关。你甚至可以使用不同的物理网络接口来隔离你的Macvlan网络。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 cat \u0026lt;\u0026lt;EOF \u0026gt; docker_network_macvlan.yaml name: docker_network_macvlan services: docker_network_macvlan: image: busybox container_name: docker_network_macvlan networks: macvlan_net: ipv4_address: 192.168.142.234 # 配置静态 IP privileged: true cap_add: - NET_ADMIN # 添加网络权限 command: sleep infinity ports: - \u0026#34;17000:17000\u0026#34; networks: macvlan_net: driver: macvlan # 使用 macvlan 类型网络 # Macvlan 网络允许你为容器分配一个MAC地址，使其在网络中像物理设备一样存在。 # Docker 守护程序可以通过容器的 MAC 地址来路由流量。 # 使用 macvlan 驱动器通常是处理那些期望直接连接到物理网络而不是通过 Docker 主机网络堆栈路由的旧应用程序时的最佳选择。 driver_opts: parent: eno1 # 指定网卡 ipam: config: - subnet: 192.168.142.0/24 # 子网 ip_range: 192.168.142.0/24 # IP 范围 gateway: 192.168.142.1 # 网关 EOF 1 docker-compose -f docker_network_macvlan.yaml up -d 📌\r重要\r该容器目前无法与使用 eno1 网卡的宿主机或其他容器通信。\nPipework\rjpetazzo/pipework: Software-Defined Networking tools for LXC (LinuX Containers) (github.com)\n1 sudo docker run -itd --name test ubuntu /bin/bash 1 sudo docker exec test ip addr show 宿主机网络为172.16.0.100，下面配置容器 test 的网络，并连接到网桥 br0 上，其中@后面是网关地址：\n1 2 sudo pipework br0 test 172.16.0.156/24@172.16.0.1 # ip addr add 172.16.0.254/24 dev br0 1 2 3 4 5 sudo ip addr add 172.16.0.100/24 dev br0; \\ sudo ip addr del 172.16.0.100/24 dev enp1s0; \\ sudo brctl addif br0 enp1s0; \\ sudo ip route del default; \\ sudo ip route add default via 172.16.0.1 dev br0 ","date":"2024-07-11T00:00:00Z","permalink":"/p/docker_macvlan_pipework_configuration/","title":"Docker 容器独立 IP 配置指南 - Macvlan 与 Pipework 实践"},{"content":"K8s 删除一个 Pod 生命周期\r删除一个容器在 Kubernetes 中涉及到以下几个步骤和概念。具体来说，容器的生命周期管理是由 Pod 和控制器（如 Deployment、ReplicaSet 等）来处理的。我们可以通过 Kubernetes 中的 kubectl delete 命令删除 Pod 或容器，具体行为会根据使用的资源类型有所不同。\n删除一个容器的过程\r假设你执行了以下命令删除一个 Pod（容器是 Pod 中的一个或多个进程）：\n1 kubectl delete pod \u0026lt;pod-name\u0026gt; 在执行此命令时，发生的过程如下：\nPod 调度管理： Kubernetes 中的控制器（如 Deployment 或 ReplicaSet）会监控与 Pod 相关的副本数，并确保运行的 Pod 数量符合期望。如果你删除了一个 Pod，而副本数设置为大于 1（比如 Deployment 中的副本数为 3），控制器会自动创建一个新的 Pod 来替代被删除的 Pod。\n如果副本数设置为 1，且没有额外的控制器来管理 Pod，那么该 Pod 被删除后不会自动重建，除非你手动调整副本数。\n容器终止： 当你删除一个 Pod 时，Kubernetes 会向 Pod 内运行的容器发送终止信号（SIGTERM），这意味着容器会有一段时间来执行清理操作。默认情况下，容器会有 30 秒的时间来处理清理操作和释放资源。\n容器的强制终止： 如果容器在 30 秒内没有停止，它会被强制终止（发送 SIGKILL 信号）。\nPod 删除： 一旦容器被终止，Pod 本身会被从集群中删除，并且相关的资源（如卷）也会被清理或重新挂载，具体取决于使用的卷类型。\n资源清理： 删除的容器会释放它所使用的资源（如 CPU、内存、网络连接等）。如果容器使用了挂载卷（如 Persistent Volumes），这些卷的清理行为也会根据其 ReclaimPolicy 和类型（如 Retain、Delete）决定。\n容器生命周期的管理\rKubernetes 中的容器生命周期通常由 Pod 控制，而 Pod 的生命周期是一个由创建、运行、终止等状态组成的过程。\nPod 生命周期\rPending： Pod 被调度到节点上，但是其中的容器尚未运行。通常是因为容器镜像需要拉取，或者资源调度未完成。 Running： 容器正在运行，Pod 正在执行。 Succeeded： 所有容器都成功终止，Pod 的生命周期结束。 Failed： 某个容器非正常退出（比如通过返回码 1 或其他非零退出状态）并且无法重新启动，Pod 进入 Failed 状态。 Unknown： 无法获取 Pod 的状态，通常是节点故障或网络问题引起的。 容器生命周期\r容器的生命周期是由 Pod 控制的，但你可以在 Pod 配置中通过 lifecycle 钩子自定义容器的生命周期行为。包括：\nPreStop： 在容器终止之前调用，可以用于执行清理工作，比如关闭网络连接、保存临时数据等。 PostStart： 在容器启动后调用，可以用于执行初始化任务。 具体生命周期示例\rPod 创建： Pod 被创建，容器开始启动。 容器开始拉取镜像并启动。 在启动过程中，容器会执行 PostStart 钩子（如果配置了）。 Pod 运行： 容器运行过程中提供服务。 在运行期间，容器和其他资源保持活跃。 Pod 删除： 当你删除 Pod 时，Kubernetes 向容器发送 SIGTERM 信号，请求容器正常关闭。 如果容器在规定时间内没有退出，则发送 SIGKILL 强制终止容器。 删除后，Pod 资源被清理（包括网络和卷资源），并且容器和 Pod 进入终止状态。 容器退出： 容器退出时，它的退出状态码会记录。若退出码为 0，则视为成功，否则为失败。 根据 Pod 配置，容器退出后，Kubernetes 可能会重新启动该容器（如果定义了重启策略，如 Always）。 重启策略与副本数\rAlways（默认）： 如果容器崩溃或被删除，Kubernetes 会尝试重新启动容器。 OnFailure： 容器只有在异常退出时（返回非零退出码）才会被重启。 Never： 容器不会自动重启，退出后将永久停止。 总结\r当你删除一个容器时，Kubernetes 会尝试按照以下步骤来管理 Pod 和容器的生命周期：\n容器优雅关闭： 发送 SIGTERM，允许容器优雅退出（默认 30 秒）。 强制终止： 如果容器在指定时间内没有退出，则发送 SIGKILL 强制停止容器。 Pod 删除： 容器停止后，Pod 资源会被清理。 控制器行为： 如果 Pod 是由 Deployment 或 ReplicaSet 管理的，控制器会确保 Pod 数量符合预期，必要时会创建新的 Pod 来替代已删除的 Pod。 如果副本数为 1，删除 Pod 后不会自动创建新的 Pod，除非手动修改副本数或重新创建资源。\n容器正常终止和强制终止\r在 Kubernetes 中，容器的终止有两个主要阶段：容器正常终止（Graceful Termination）和容器强制终止（Forced Termination）。这两者的区别主要体现在 Kubernetes 如何处理容器的退出过程，以及容器退出时是否能有机会进行清理操作。\n容器正常终止（Graceful Termination）\r当你删除一个 Pod 或者容器时，Kubernetes 会首先尝试以一种优雅的方式终止容器，这通常被称容器正常终止**（Graceful Termination）**。\n发送 SIGTERM 信号： 当 Kubernetes 请求终止容器时，它会向容器发送一个 SIGTERM（终止信号）。这时，容器可以捕捉到该信号并开始进行正常的关闭操作。\n容器可以在接收到 SIGTERM 后执行一系列操作，比如：\n关闭网络连接。 清理占用的资源（例如文件句柄、临时数据等）。 执行自定义的关闭逻辑（例如数据库提交、日志记录等）。 Grace Period（宽限期）： 在接收到 SIGTERM 信号后，容器会有一个“宽限期”来完成这些清理工作。默认情况下，这个宽限期为 30 秒，但你可以在 Pod 配置文件中通过 terminationGracePeriodSeconds 字段来调整这个时间。\n正常退出： 如果容器在宽限期内正常退出（即，执行了清理操作并退出），Kubernetes 会将容器状态标记为 Succeeded 或 Terminated，Pod 会根据终止策略进行后续处理（如删除或重新调度）。\n容器强制终止（Forced Termination）\r如果容器没有在宽限期内正常退出，Kubernetes 会进行强制终止。这意味着容器没有完成清理操作或未响应终止请求时，Kubernetes 会采取更强硬的措施来终止容器。\n发送 SIGKILL 信号： 如果容器没有在 terminationGracePeriodSeconds 指定的时间内优雅地终止，Kubernetes 会发送 SIGKILL（强制杀死信号）来立即停止容器。与 SIGTERM 不同，SIGKILL 信号无法被捕获或处理，因此容器无法执行任何清理操作。 立即终止： 当容器收到 SIGKILL 信号后，它会被立即停止，所有正在运行的进程会被杀死。这意味着容器的进程没有机会释放资源（如文件句柄、临时存储等），并且可能会留下未清理的状态。 无法清理资源： 容器在强制终止时没有机会进行任何的清理操作，可能会导致一些资源（例如临时文件、内存、数据库连接等）没有正确释放。 容器正常终止与强制终止的区别\r特性 正常终止（Graceful Termination） 强制终止（Forced Termination） 信号 SIGTERM SIGKILL 清理时间 容器有机会进行清理，默认为 30 秒（可配置） 没有清理时间，立即终止 终止操作 容器可以捕获 SIGTERM，执行清理操作 容器无法捕获 SIGKILL，直接强制终止 资源释放 容器能够释放资源（如文件句柄、数据库连接等） 容器无法释放资源，可能会泄漏资源 容器状态 容器可以正常退出，状态为 Succeeded 或 Terminated 容器强制停止，状态为 Terminated 发生场景 容器运行正常，接收到删除请求时的正常处理 容器没有在宽限期内退出或没有响应请求 Pod 配置中如何控制容器终止行为\r你可以在 Pod 的配置文件中设置 terminationGracePeriodSeconds 来控制容器的宽限期。默认情况下，这个值是 30 秒，但你可以根据需要进行修改。\n1 2 3 4 5 6 7 8 9 apiVersion: v1 kind: Pod metadata: name: mypod spec: terminationGracePeriodSeconds: 60 # 设置容器的优雅终止宽限期为 60 秒 containers: - name: mycontainer image: myimage 容器生命周期钩子（Lifecycle Hooks）\rKubernetes 还提供了 lifecycle 钩子，允许你在容器的启动和停止过程中插入自定义行为，特别是在容器终止时执行额外操作：\nPreStop： 在容器终止之前执行，你可以使用此钩子来执行一些清理操作，或者发送通知等操作。例如，停止某个外部服务的连接或关闭数据库连接。 示例：\n1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: mycontainer image: myimage lifecycle: preStop: exec: command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;echo \u0026#39;Container is stopping\u0026#39; \u0026gt; /tmp/shutdown.log\u0026#34;] 总结\r容器正常终止： Kubernetes 会通过发送 SIGTERM 信号请求容器优雅退出，容器有机会进行清理操作并释放资源，通常会在宽限期内完成。 容器强制终止： 如果容器没有在宽限期内正常退出，Kubernetes 会发送 SIGKILL 信号强制停止容器，容器无法进行任何清理操作，立即停止并释放资源。 通过合理配置 Pod 的宽限期和生命周期钩子，你可以控制容器的终止行为，并确保在容器退出时尽量避免资源泄漏和服务中断。\nSIGTERM 和 SIGKILL\rSIGTERM 和 SIGKILL 是两种常用的 Unix/Linux 信号，它们都用于向进程发送终止请求，但它们的作用、行为和后果是不同的。以下是对这两种信号的详细介绍：\nSIGTERM (Signal Terminate) - 信号终止\r编号： 15\n作用： SIGTERM 是一种请求进程终止的信号。它通知进程优雅地退出，即让进程有时间完成清理工作、保存状态和释放资源。此信号是终止进程的默认信号。\n发送方式： 你可以使用 kill 命令发送 SIGTERM 信号，或者在程序中使用 kill(pid, SIGTERM) 来发送信号。\n示例：\n1 kill \u0026lt;pid\u0026gt; # 默认发送 SIGTERM 信号 特点：\nSIGTERM 允许进程进行优雅的退出，它可以被进程捕获并处理。 进程可以在接收到 SIGTERM 后执行清理操作，例如关闭数据库连接、保存临时数据、关闭文件句柄等。 如果进程捕捉到 SIGTERM，它可以执行必要的清理工作后正常退出。 如果进程没有响应 SIGTERM，操作系统会默认等待一段时间（通常是 30 秒），然后发送 SIGKILL。 响应方式：\n进程可以选择捕捉并处理 SIGTERM，比如通过注册一个信号处理函数。 进程也可以选择忽略 SIGTERM，但如果它未在规定时间内退出，操作系统将发送 SIGKILL。 应用场景：\n当你希望让进程有机会进行清理操作后退出时使用，例如关闭网络连接、保存数据、日志记录等。 在 Kubernetes 或 Docker 中，当你删除一个容器时，系统会首先发送 SIGTERM 请求容器优雅地停止。 SIGKILL (Signal Kill) - 信号强制终止\r编号： 9\n作用： SIGKILL 是一个强制终止进程的信号，直接要求操作系统停止目标进程，而不允许进程处理任何清理操作。它是无法被捕捉、忽略或处理的。\n示例：\n1 kill -9 \u0026lt;pid\u0026gt; # 发送 SIGKILL 信号 特点：\nSIGKILL 是强制终止进程的信号，进程无法进行任何清理操作。 无论进程是否响应，操作系统都会立刻终止进程，释放其占用的资源。 进程无法捕获或忽略 SIGKILL 信号，它会立即将进程从内存中移除。 发送 SIGKILL 信号后，进程不会有机会关闭文件、释放内存、写入日志或执行其他必要的清理操作。 应用场景：\n当进程无法正常响应 SIGTERM 或已经挂起（例如卡死或进入无限循环时），使用 SIGKILL 来强制终止进程。 在操作系统中，SIGKILL 用于终止无响应的或僵尸进程，确保它们被完全清理。 对比：SIGTERM 与 SIGKILL\r特性 SIGTERM (15) SIGKILL (9) 作用 请求进程优雅退出，允许进程处理清理操作 强制终止进程，无法被进程捕获或处理 是否可以捕捉/处理 可以被进程捕捉，进程可以自定义终止逻辑 无法捕捉或处理，进程立刻被终止 优雅退出 允许进程进行资源清理、关闭文件、保存数据等 进程立刻被终止，不执行任何清理操作 是否能被忽略 可以被进程忽略（如果没有捕捉 SIGTERM 信号） 不能被忽略或处理，强制终止 默认行为 系统通常等待进程在指定时间内退出（默认30秒） 立即终止进程，不等待 应用场景 需要优雅关闭进程、释放资源、保存状态等 强制终止无法退出的进程，或进程无法响应 SIGTERM 操作系统行为 操作系统等待进程在规定时间内退出 操作系统立即杀死进程，释放所有资源 Kubernetes 中的 SIGTERM 和 SIGKILL\r在 Kubernetes 中，当你删除一个 Pod 或容器时，Kubernetes 会首先向容器发送 SIGTERM 信号，要求容器的主进程优雅地退出。容器内的进程有一定的时间（默认30秒）来响应 SIGTERM 并执行清理操作。如果容器在这个宽限期内没有正常退出，Kubernetes 会发送 SIGKILL 来强制终止容器。这个过程的目标是确保尽量让容器优雅关闭，但如果它无法响应，则使用强制方式来终止。\nSIGTERM 信号\r发送对象： 容器内的主进程。\n行为： 容器运行时，会有一个主进程（通常是容器启动时定义的 CMD 或 ENTRYPOINT 进程）。当 Kubernetes 请求停止容器时，它会向容器的主进程发送 SIGTERM 信号。SIGTERM 是一个优雅的终止信号，意味着容器内的进程应当处理它，进行清理操作，并正常退出。\n容器内的行为： 容器的主进程有机会捕捉到 SIGTERM 信号，并在适当的时间内执行清理操作，例如：\n关闭数据库连接。 保存日志文件或持久数据。 清理临时文件或缓存。 容器可以通过捕捉信号并在接收到 SIGTERM 后执行自定义的终止逻辑（例如通过生命周期钩子 PreStop）。\n宽限期（Grace Period）： Kubernetes 默认给容器主进程 30 秒（可以配置）来优雅地关闭。如果容器内的进程在这个时间内没有退出，Kubernetes 将会发送 SIGKILL 信号，强制终止容器。\nSIGKILL 信号\r发送对象： 容器内的进程（主进程或任何其他进程）。 行为： SIGKILL 是一个无法被捕获或忽略的信号。它会直接杀死容器内的进程，且不允许容器内的进程进行任何清理操作。当容器在 SIGTERM 信号后的宽限期内没有正常退出时，Kubernetes 会发送 SIGKILL 来强制终止容器内的进程。 容器内进程行为： 容器内的进程在接收到 SIGKILL 信号时，会立即停止，无法做任何清理工作。所有的文件句柄、网络连接、数据库事务等可能都会被中断，导致资源没有正常释放。 容器的终止： 因为容器内的进程已经被强制终止，容器也会结束生命周期。此时，Pod 控制器（如 Deployment 或 ReplicaSet）会根据副本数重新调度新的容器。 容器与进程的关系\r在 Kubernetes 中，容器实际上是一个运行时环境，封装了一些进程（通常只有一个主进程）。当我们讨论容器的启动、停止或重启时，实际上是指管理容器内的进程的生命周期。 当 Kubernetes 发送 SIGTERM 或 SIGKILL 时，它是对容器内的进程发出的信号，而不是直接发给容器本身。容器本身并没有执行的代码或进程，它只是一个运行时环境，容器内的进程才是处理这些信号的对象。 容器内的进程如何响应 SIGTERM 和 SIGKILL\r响应 SIGTERM： 容器内的进程可以捕捉到 SIGTERM 信号。容器主进程可以通过代码逻辑来处理这个信号，进行清理操作或释放资源。\n例如，如果容器中运行的是一个 HTTP 服务器进程，它可能会在接收到 SIGTERM 后，停止接受新请求并优雅地完成当前正在处理的请求，然后退出。\n示例代码（Python）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 import signal import time def graceful_shutdown(signum, frame): print(\u0026#34;Received SIGTERM, shutting down gracefully...\u0026#34;) # 执行清理操作，比如关闭数据库连接、清理缓存等 time.sleep(2) # 模拟清理工作 exit(0) signal.signal(signal.SIGTERM, graceful_shutdown) print(\u0026#34;Running... Press CTRL+C to exit.\u0026#34;) while True: time.sleep(1) 响应 SIGKILL： 容器内的进程无法捕捉或处理 SIGKILL 信号。SIGKILL 信号直接杀死进程，容器中的进程会立即退出，没有机会进行清理操作。\n在接收到 SIGKILL 时，进程不会有时间保存状态或清理资源，系统会立即终止进程，且无法进行任何形式的恢复。\n总结\rSIGTERM： 是一个优雅的请求，允许进程清理资源并优雅地退出。如果进程在规定时间内没有退出，操作系统会发送 SIGKILL。 SIGKILL： 是一个强制终止的信号，进程无法响应或捕捉，进程会被立即终止，无法进行任何清理工作。 这两种信号是 Unix/Linux 系统管理中常用的信号，理解它们的作用和行为可以帮助你更有效地管理进程的生命周期，尤其是在容器化环境（如 Kubernetes）和自动化运维中。\nSIGTERM 和 SIGKILL 信号，虽然在 Kubernetes 中通常是通过容器管理的，但它们实际上是发给 容器内运行的进程 的信号，而不是直接发送给容器本身。容器是一个包含一个或多个进程的运行环境，因此，容器中的进程是这些信号的实际接收者。\nSIGTERM 信号 发送给容器中的进程，允许它们优雅地退出并执行清理操作。如果容器内的进程在一定时间内（默认为 30 秒）没有退出，Kubernetes 会发送 SIGKILL。 SIGKILL 信号 也是发送给容器内的进程，但它是无法被捕捉或忽略的，容器内的进程会被立即强制终止，并且不会有机会执行任何清理操作。 最终，容器作为运行时环境，管理的是容器内的进程，因此容器的生命周期管理依赖于容器内进程如何响应这些信号。\n","date":"2024-03-27T00:00:00Z","permalink":"/p/kubernetes_pod_termination_signals/","title":"Kubernetes中Pod终止信号量详解"},{"content":"日志格式\n1 2024-01-29 16:11:11.189 |INFO | 1.1.1.1|2345 | com.smart.service.receive.impl.ReceiveServiceImpl:903 | 能力\u0026gt;总共04步 | 6df2f14fca4b40f6be89b9ef19382c42adasfasf logstash\rdocker方式部署\r1 2 3 4 5 6 7 8 9 10 11 12 13 [root@master logstash]# cat docker-compose.yaml version: \u0026#39;3\u0026#39; services: logstash: image: docker.elastic.co/logstash/logstash:8.12.0 container_name: logstash volumes: - ./conf/logstash.yml:/usr/share/logstash/config/logstash.yml - ./conf/conf.d:/usr/share/logstash/config/conf.d/ - ./logs:/opt ports: - 5044:5044 配置文件\nlogstash.yml\n1 2 3 http.host: \u0026#34;0.0.0.0\u0026#34; xpack.monitoring.elasticsearch.hosts: [ \u0026#34;http://192.168.142.106:9200\u0026#34; ] path.config: /usr/share/logstash/config/conf.d/*.conf collect.conf\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 input { file { type =\u0026gt; \u0026#34;info_log\u0026#34; path =\u0026gt; \u0026#34;/opt/kaikai.log\u0026#34; discover_interval =\u0026gt; 10 # 监听间隔 start_position =\u0026gt; \u0026#34;end\u0026#34; # sincedb_path =\u0026gt; \u0026#34;/usr/share/logstash/sincedb_kaikai\u0026#34; #start_position =\u0026gt; \u0026#34;beginning\u0026#34; codec =\u0026gt; multiline { pattern =\u0026gt; \u0026#34;^%{TIMESTAMP_ISO8601}\u0026#34; negate =\u0026gt; true what =\u0026gt; \u0026#34;previous\u0026#34; } } file { type =\u0026gt; \u0026#34;error_log\u0026#34; path =\u0026gt; \u0026#34;/opt/error.log\u0026#34; discover_interval =\u0026gt; 10 start_position =\u0026gt; \u0026#34;beginning\u0026#34; codec =\u0026gt; multiline { pattern =\u0026gt; \u0026#34;^%{TIMESTAMP_ISO8601}\u0026#34; negate =\u0026gt; true what =\u0026gt; \u0026#34;previous\u0026#34; } } } filter { grok { match =\u0026gt; { \u0026#34;[log][file][path]\u0026#34; =\u0026gt; \u0026#34;/(?\u0026lt;logfilename\u0026gt;[^/]+)\\.log$\u0026#34; } # 获取文件名logfilename } grok { match =\u0026gt; { \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;%{DATA:time}\\|%{DATA:level}\\|%{DATA:ip}\\|%{DATA:pid}\\|%{DATA:source}\\|%{GREEDYDATA:content}\u0026#34;} } if \u0026#34;_grokparsefailure\u0026#34; in [tags] { mutate { add_field =\u0026gt; { \u0026#34;content\u0026#34; =\u0026gt; \u0026#34;%{message}\u0026#34; } add_field =\u0026gt; { \u0026#34;level\u0026#34; =\u0026gt; \u0026#34;ERROR\u0026#34; } } } } output { stdout { codec =\u0026gt; rubydebug } elasticsearch { hosts =\u0026gt; [\u0026#34;192.168.142.106:9200\u0026#34;] index =\u0026gt; \u0026#34;%{logfilename}-%{+YYYY-MM-dd}\u0026#34; # 以文件名为索引 } } k8s部署\r作为filebeat接收收集器日志处理 logstash.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 apiVersion: v1 kind: ConfigMap metadata: name: log-file-config data: logstash.yml: | http.host: \u0026#34;0.0.0.0\u0026#34; xpack.monitoring.elasticsearch.hosts: [ \u0026#34;http://192.168.142.106:9200\u0026#34; ] #xpack.monitoring.elasticsearch.hosts: [ \u0026#34;http://192.168.142.106:9200\u0026#34; ] path.config: /usr/share/logstash/config/conf.d/*.conf collect.conf: | input { beats { port =\u0026gt; 5044 } } filter { grok { match =\u0026gt; { \u0026#34;[log][file][path]\u0026#34; =\u0026gt; [\u0026#34;/(?\u0026lt;logfilename\u0026gt;[^/]+)\\.log$\u0026#34;] } } grok { match =\u0026gt; { \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;%{DATA:time}\\|%{DATA:level}\\|%{DATA:ip}\\|%{DATA:pid}\\|%{DATA:source}\\|%{GREEDYDATA:content}\u0026#34; } } if \u0026#34;_grokparsefailure\u0026#34; in [tags] { mutate { add_field =\u0026gt; { \u0026#34;content\u0026#34; =\u0026gt; \u0026#34;%{message}\u0026#34; } add_field =\u0026gt; { \u0026#34;level\u0026#34; =\u0026gt; \u0026#34;ERROR\u0026#34; } } } } output { stdout { codec =\u0026gt; rubydebug } elasticsearch { hosts =\u0026gt; [\u0026#34;192.168.142.106:9200\u0026#34;] index =\u0026gt; \u0026#34;%{logfilename}-%{+YYYY-MM-dd}\u0026#34; } } --- kind: Deployment apiVersion: apps/v1 metadata: name: logstash labels: app: logstash spec: replicas: 4 selector: matchLabels: app: logstash template: metadata: labels: app: logstash annotations: appName: logstash appType: java spec: containers: - name: logstash-logging image: registry.cn-beijing.aliyuncs.com/kaikai136/logstash:8.12.0 volumeMounts: - name: logstash-config mountPath: /usr/share/logstash/config/logstash.yml subPath: logstash.yml - name: logstash-config mountPath: /usr/share/logstash/config/conf.d/collect.conf subPath: collect.conf volumes: - name: logstash-config configMap: name: log-file-config items: - key: logstash.yml path: logstash.yml - key: collect.conf path: collect.conf imagePullSecrets: - name: my-harbor --- apiVersion: v1 kind: Service metadata: name: logstash-svc labels: app: logstash-svc spec: ports: - port: 5044 targetPort: 5044 protocol: TCP name: http nodePort: 32467 type: NodePort selector: app: logstash filebeat\r收集器测试\rfilebeat.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 apiVersion: v1 kind: ConfigMap metadata: name: filebeat-config data: filebeat.yml: | filebeat.inputs: - type: log enabled: true paths: - /logs/*_info.log scan_frequency: 1s # 将扫描频率设置为1秒 harvester_buffer_size: 32768 # 增加harvester的缓冲区大小 backoff_factor: 2 ignore_older: 24h # 忽略超过24小时未修改的文件 close_inactive: 5m # 关闭5分钟内无活动的harvester clean_inactive: 72h # 清理超过72小时无活动的harvester close_removed: true # 当文件被删除时关闭harvester clean_removed: true # 清理被删除的harvester close_eof: true # 当文件达到EOF时关闭harvester multiline.pattern: \u0026#39;^[0-9]{4}\u0026#39; # 匹配多行日志 multiline.negate: true multiline.match: after var.convert_timezone: true # 转换时区 encoding: UTF-8 # 设置编码 fields: wisentIp: 0.0.0.0 # 添加自定义字段 log_type: info_log - type: log enabled: true paths: - /logs/*_error.log scan_frequency: 1s # 将扫描频率设置为1秒 harvester_buffer_size: 32768 # 增加harvester的缓冲区大小 backoff_factor: 2 ignore_older: 24h # 忽略超过24小时未修改的文件 close_inactive: 5m # 关闭5分钟内无活动的harvester clean_inactive: 72h # 清理超过72小时无活动的harvester close_removed: true # 当文件被删除时关闭harvester clean_removed: true # 清理被删除的harvester close_eof: true # 当文件达到EOF时关闭harvester multiline.pattern: \u0026#39;^[0-9]{4}\u0026#39; # 匹配多行日志 multiline.negate: true multiline.match: after var.convert_timezone: true # 转换时区 encoding: UTF-8 # 设置编码 fields: wisentIp: 0.0.0.0 # 添加自定义字段 log_type: error_log queue.mem: events: 4096 # 内存队列大小 flush.min_events: 2048 # 最小刷新事件数 flush.timeout: 1s # 刷新超时 #queue.disk: # max_size: 1024mb # 最大磁盘使用空间 # segment_size: 10mb # 每个段的大小 # max_retries: 3 # 最大重试次数 logging.level: debug filebeat.shutdown_timeout: 30s # 在关闭Filebeat时，确保有足够的时间处理完当前的事件 throttle: 5s # 设置Filebeat在被节流之前等待的时间 logging.level: info # 设置日志级别为info以获得详细的运行信息 logging.to_files: true logging.files: path: /usr/share/filebeat/logs name: filebeat keepfiles: 7 permissions: 0644 output.logstash: hosts: [\u0026#34;logstash-svc.default.svc.cluster.local:5044\u0026#34;] --- kind: Deployment apiVersion: apps/v1 metadata: name: filebeat labels: app: filebeat spec: replicas: 1 selector: matchLabels: app: filebeat template: metadata: labels: app: filebeat annotations: appName: filebeat appType: java spec: containers: - name: filebeat-logging image: registry.cn-beijing.aliyuncs.com/kaikai136/filebeat:8.12.0 volumeMounts: - name: filebeat-config mountPath: /usr/share/filebeat/filebeat.yml subPath: filebeat.yml - name: myhostpath mountPath: /logs volumes: - name: filebeat-config configMap: name: filebeat-config items: - key: filebeat.yml path: filebeat.yml - name: myhostpath hostPath: path: /opt/kaikai/file-logstash/filebeat_log type: DirectoryOrCreate imagePullSecrets: - name: my-harbor ","date":"2024-03-09T00:00:00Z","permalink":"/p/logstash_filebeat_configuration/","title":"日志收集器Logstash与Filebeat部署配置指南"},{"content":"本指南介绍如何在 Kubernetes 集群中配置 Ingress-Nginx 控制器以支持 TCP 服务转发。以 MySQL 服务为例进行说明。\n前提条件\r已安装 Kubernetes 集群 已部署 Ingress-Nginx 控制器 已有需要暴露的 TCP 服务（本例中使用 MySQL） 配置步骤\r配置 TCP 服务映射\r创建 tcp-services.yaml 文件，定义 TCP 端口映射：\n1 2 3 4 5 6 7 8 apiVersion: v1 kind: ConfigMap metadata: name: tcp-services namespace: ingress-nginx data: # 格式：外部端口: \u0026#34;目标命名空间/服务名:目标端口\u0026#34; 3306: \u0026#34;default/mysql-primary:3306\u0026#34; 应用配置：\n1 kubectl create -f tcp-services.yaml 如果 ConfigMap 已存在，可以直接编辑：\n1 2 kubectl edit configmap tcp-services -n ingress-nginx # 在 data 部分添加新的映射: \u0026lt;外部端口\u0026gt;: \u0026#34;\u0026lt;命名空间\u0026gt;/\u0026lt;服务名\u0026gt;:\u0026lt;端口\u0026gt;\u0026#34; 更新 Ingress Controller 配置\r编辑 Ingress-Nginx Controller Deployment：\n1 kubectl edit deployment ingress-nginx-controller -n ingress-nginx 在 controller 的 args 部分添加以下配置：\n1 2 3 args: # ... 其他现有参数 ... - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services 配置 Ingress Controller Service\r编辑 Ingress-Nginx Controller Service：\n1 kubectl edit service ingress-nginx-controller -n ingress-nginx 在 ports 部分添加 TCP 服务端口：\n1 2 3 4 5 6 ports: # ... 其他现有端口配置 ... - name: mysql-primary port: 3306 protocol: TCP targetPort: 3306 验证配置\r完成配置后，可以通过以下方式验证：\n检查 ConfigMap 是否正确创建： 1 kubectl get configmap tcp-services -n ingress-nginx -o yaml 确认 Ingress Controller 是否正常运行： 1 kubectl get pods -n ingress-nginx 验证端口是否正确开放： 1 kubectl get svc ingress-nginx-controller -n ingress-nginx 注意事项\r确保目标服务（如 MySQL）已经在集群中正常运行 注意检查端口冲突 如果使用云服务提供商，可能需要额外配置安全组或防火墙规则 ","date":"2023-12-20T00:00:00Z","permalink":"/p/k8s_ingress_tcp_forwarding_config/","title":"Kubernetes Ingress-Nginx TCP服务转发配置指南"},{"content":"安卓-iOS-macOS Docker\rhttps://github.com/sickcodes/dock-droid https://github.com/remote-android/redroid-doc https://github.com/budtmo/docker-android iOS Docker macOS Docker Redroid\r非 Root 版本\r启动\n1 2 3 4 5 6 7 8 9 10 11 12 version: \u0026#34;3.0\u0026#34; services: redroid_11: image: redroid/redroid:11.0.0-latest # 安卓 11 container_name: redroid_11 privileged: true restart: always ports: - 5555:5555 volumes: - ../data/redroid_11:/data command: ro.secure=0 Root 版本\r初始化脚本\r路径：~/Redroid/MagiskOnRedroid/\n需要注意机器的 CPU 架构\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 #!/bin/bash # 删除旧的文件 rm -rf ./magisk* ./remove.rc ./setup.sh # 创建一个目录，用于存放 magisk 的文件 if [ ! -d \u0026#34;/root/Redroid/MagiskOnRedroid\u0026#34; ]; then mkdir ~/Redroid/MagiskOnRedroid fi cd ~/Redroid/MagiskOnRedroid # 下载 magisk 的 apk 文件 find -maxdepth 1 -iname \u0026#34;magisk*\u0026#34; -not -name \u0026#34;*.apk\u0026#34; -exec rm -r {} \\; magisk_file=\u0026#34;app-debug.apk\u0026#34; if [ ! -f $magisk_file ]; then wget \u0026#34;https://cdn.jsdelivr.net/gh/topjohnwu/magisk-files@1cea72840fbf690f9a95512d03721f6a710fe02e/app-debug.apk\u0026#34; fi # 解压 magisk 的 apk 文件：注意机器的架构，这里是 x86_64 unzip -j $magisk_file \u0026#34;lib/x86_64/libmagisk64.so\u0026#34; -d magisk unzip -j $magisk_file \u0026#34;lib/x86_64/libbusybox.so\u0026#34; -d magisk mv -v magisk/libmagisk64.so magisk/magisk mv -v magisk/libbusybox.so magisk/busybox # 压缩 magisk 的文件 tar --transform \u0026#39;s/.*\\///g\u0026#39; -cf ./magisk.tar --absolute-names $( find ~/Redroid/MagiskOnRedroid | grep -E \u0026#34;magisk/|app-debug.apk$\u0026#34; ) # remove.rc cat \u0026lt;\u0026lt;\\EOF \u0026gt; ./remove.rc on early-init export PATH /sbin:/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin chmod 0700 /magisk.tar chown root root /magisk.tar chmod 0700 /setup.sh chown root root /setup.sh exec root root -- /setup.sh service magisk-d /sbin/magisk --daemon user root oneshot on boot start magisk-d on post-fs-data start logd rm /dev/.magisk-unblock start s1 wait /dev/.magisk-unblock 5 rm /dev/.magisk-unblock service s1 /sbin/magisk --post-fs-data user root oneshot service s2 /sbin/magisk --service class late_start user root oneshot on property:sys.boot_completed=1 exec /sbin/magisk --boot-complete on property:init.svc.zygote=restarting exec /sbin/magisk --zygote-restart on property:init.svc.zygote=stopped exec /sbin/magisk --zygote-restart EOF sudo chmod 644 ./remove.rc sudo chown root:root ./remove.rc # setup.sh cat \u0026lt;\u0026lt;\\EOF \u0026gt; ./setup.sh #!/system/bin/sh # rm /system/fonts/NotoColorEmoji.ttf tmpPushed=/magisk rm -rf $tmpPushed mkdir $tmpPushed tar -xvf /magisk.tar --no-same-owner -C $tmpPushed umount /magisk.tar ; rm -v /magisk.tar mkdir /sbin chown root:root /sbin # chmod 0700 /sbin chmod 0751 /sbin cp $tmpPushed/magisk /sbin/ cp $tmpPushed/app-debug.apk /sbin/stub.apk find /sbin -type f -exec chmod 0755 {} \\; find /sbin -type f -exec chown root:root {} \\; # add /sbin # /sbin/ # ├── magisk # └── stub.apk ln -f -s /sbin/magisk /system/xbin/su mkdir /product/bin chmod 751 /product/bin ln -f -s /sbin/magisk /product/bin/su # add su (override `/system/xbin/su`) # /product/bin/ # └── su -\u0026gt; /sbin/magisk mkdir -p /data/adb/magisk chmod 700 /data/adb mv $tmpPushed/busybox /data/adb/magisk/ chmod -R 755 /data/adb/magisk chmod -R root:root /data/adb/magisk # /data/adb/ # ├── magisk # │ └── busybox # rm -rf $tmpPushed EOF sudo chmod 700 ./setup.sh sudo chown root:root ./setup.sh 启动\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 version: \u0026#34;3.0\u0026#34; services: redroid_11_magisk: image: redroid/redroid:11.0.0-latest # 安卓 11 container_name: redroid_11_magisk privileged: true restart: always ports: - 5555:5555 volumes: - ../data/redroid_11_magisk:/data - ../MagiskOnRedroid/remove.rc:/vendor/etc/init/remove.rc - ../MagiskOnRedroid/setup.sh:/setup.sh - ../MagiskOnRedroid/magisk.tar:/magisk.tar command: ro.secure=0 连接模拟器\r1 2 adb connect 172.16.0.101:5555 scrcpy -s 172.16.0.101:5555 ","date":"2023-12-01T00:00:00Z","image":"/p/android_redroid_docker_configuration/android_redroid_docker_configuration.png","permalink":"/p/android_redroid_docker_configuration/","title":"安卓Redroid容器化实现指南"},{"content":"在Linux系统安全维护中，防火墙是保护服务器免受未授权访问的重要工具。iptables作为Linux内核的防火墙工具，提供了强大而灵活的网络流量控制机制。本文将深入剖析iptables的核心概念、配置方法及常见应用场景，帮助系统管理员构建更安全的服务器环境。\n什么是iptables\riptables是Linux系统中的防火墙工具，它直接与Linux内核的netfilter模块交互，负责对网络数据包进行过滤、修改和转发。作为一个完整的防火墙框架，iptables提供了细粒度的网络流量控制能力，能够根据多种条件（如源IP地址、目标端口、协议类型等）来制定复杂的网络访问策略。\niptables的核心组件\riptables的结构基于\u0026quot;表\u0026quot;和\u0026quot;链\u0026quot;的概念：\n表（Tables）：用于组织具有特定功能的规则\nfilter：默认表，用于数据包过滤 nat：用于网络地址转换 mangle：用于特殊数据包修改 raw：用于配置豁免连接跟踪 security：用于强制访问控制网络规则 链（Chains）：每个表包含多个链，链定义了何时应用规则\nINPUT：处理发往本机的数据包 OUTPUT：处理本机发出的数据包 FORWARD：处理经过本机转发的数据包 PREROUTING：路由前处理 POSTROUTING：路由后处理 iptables基本操作\r查看当前规则\r1 2 3 4 5 # 查看filter表的所有规则 sudo iptables -L -v # 查看特定表的规则 sudo iptables -t nat -L -v 添加规则\r1 2 3 4 5 6 7 8 # 允许SSH连接（22端口） sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT # 允许已建立的连接 sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 允许本地回环接口 sudo iptables -A INPUT -i lo -j ACCEPT 删除规则\r1 2 3 4 5 # 删除第1条规则 sudo iptables -D INPUT 1 # 删除特定规则 sudo iptables -D INPUT -p tcp --dport 80 -j ACCEPT 设置默认策略\r1 2 3 4 5 # 设置INPUT链默认拒绝 sudo iptables -P INPUT DROP # 设置OUTPUT链默认允许 sudo iptables -P OUTPUT ACCEPT 常见应用场景和配置示例\r基础服务器保护配置\r以下是一个基础的服务器保护配置示例，允许常见服务并默认拒绝其他连接：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 清除现有规则 sudo iptables -F sudo iptables -X # 设置默认策略 sudo iptables -P INPUT DROP sudo iptables -P FORWARD DROP sudo iptables -P OUTPUT ACCEPT # 允许本地回环 sudo iptables -A INPUT -i lo -j ACCEPT # 允许已建立的连接 sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 允许SSH (22端口) sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT # 允许HTTP/HTTPS (80/443端口) sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT # 允许ICMP (ping) sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT 端口转发配置\r将外部80端口请求转发到内部8080端口：\n1 2 3 4 5 # 启用IP转发 echo 1 \u0026gt; /proc/sys/net/ipv4/ip_forward # 设置端口转发 sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080 限制连接速率\r防止DoS攻击的简单配置：\n1 2 3 # 限制SSH连接速率，每分钟最多3个新连接 sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP 禁止特定IP地址\r1 2 3 4 5 # 封禁特定IP sudo iptables -A INPUT -s 192.168.1.100 -j DROP # 封禁IP段 sudo iptables -A INPUT -s 192.168.1.0/24 -j DROP 规则持久化\riptables规则在系统重启后会丢失，需要进行持久化配置：\nDebian/Ubuntu系统\r1 2 3 4 5 # 安装iptables-persistent sudo apt-get install iptables-persistent # 保存当前规则 sudo netfilter-persistent save CentOS/RHEL系统\r1 2 3 4 5 # 保存当前规则 sudo service iptables save # 或使用 sudo iptables-save \u0026gt; /etc/sysconfig/iptables 常见问题排查\r规则顺序问题：iptables按顺序匹配规则，一旦匹配成功就停止。因此规则顺序至关重要。\n锁定风险：添加DROP规则时需谨慎，错误配置可能导致无法远程连接服务器。建议在有物理访问权限时测试新规则。\n性能考虑：过多的规则会影响网络性能，应定期清理不必要的规则。\n日志记录：使用LOG目标记录被拒绝的连接，有助于排查问题：\n1 sudo iptables -A INPUT -j LOG --log-prefix \u0026#34;iptables denied: \u0026#34; --log-level 7 高级功能\r网络地址转换（NAT）\r配置简单的NAT以共享Internet连接：\n1 2 3 4 5 6 7 8 # 启用IP转发 echo 1 \u0026gt; /proc/sys/net/ipv4/ip_forward # 设置源地址转换 sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # 允许转发从内网接口到外网接口的流量 sudo iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT 流量控制和QoS\r使用iptables的mangle表标记流量，配合tc（Traffic Control）实现QoS：\n1 2 # 标记SSH流量 sudo iptables -t mangle -A PREROUTING -p tcp --dport 22 -j MARK --set-mark 1 结论\riptables是Linux系统中功能强大的防火墙工具，掌握它的使用对于服务器安全至关重要。通过合理配置iptables规则，可以有效控制网络流量、保护服务器免受未授权访问，同时实现网络地址转换等高级功能。\n在实际应用中，应根据具体需求设计规则，并定期检查和更新防火墙配置。随着网络威胁不断演变，防火墙策略也应相应调整，以保持最佳安全状态。\n对于更复杂的场景，可以考虑使用更高级的前端工具如ufw（Uncomplicated Firewall）或firewalld，它们在底层仍使用iptables，但提供了更友好的接口。\n无论使用何种工具，理解iptables的核心概念和工作原理，都是构建安全Linux网络环境的基础。\n","date":"2023-10-03T00:00:00Z","permalink":"/p/linux_firewall_iptables_guide/","title":"Linux防火墙工具iptables使用指南与配置详解"},{"content":"基于 Prometheus 的全方位监控平台\u0026ndash;基于 Consul 的自动发现\n背景\rConsul Documentation | Consul | HashiCorp Developer\nPrometheus配置文件 prometheus-config.yaml 配置了大量的采集规则，基本上都是运维小伙伴手动处理，如果后面增加了节点或者组件信息，就得手动修改此配置，并热加载 promethues；那么能否动态的监听微服务呢？Prometheus 提供了多种动态服务发现的功能，这里以 consul 为例。\n基于Consul的自动发现\rConsul是分布式k/v数据库，是一个服务注册组件，其他服务都可以注册到consul上，Prometheus也不例外，通过consul的服务发现，我们可以避免在Prometheus中指定大量的target。\nprometheus基于consul的服务发现流程如下：\n在consul注册服务或注销服务（监控targets） Prometheus一直监视consul服务，当发现consul中符合要求的服务有新变化就会更新Prometheus监控对象 Prometheus 支持的多种服务发现机制\rPrometheus数据源的配置主要分为 静态配置 和 动态发现 , 常用的为以下几类:\n1 2 3 4 5 1）static_configs: #静态服务发现 2）file_sd_configs: #文件服务发现 3）dns_sd_configs: DNS #服务发现 4）kubernetes_sd_configs: #Kubernetes 服务发现 5）consul_sd_configs: Consul #服务发现 在监控kubernetes的应用场景中，频繁更新的pod，svc，等资源配置应该是最能体现Prometheus监控目标自动发现服务的好处。\n工作原理\r1、Prometheus通过Consul API查询Consul的KV存储中保存的配置信息，然后从中获取关于服务的元数据；\n2、Prometheus使用这些信息来构造目标服务的URL，并将其添加到服务发现的目标列表中。\n3、当服务被注销或不可用时，Prometheus将自动从其目标列表中删除该服务。\n容器化 Consul 集群\r测试验证，不可作为线上使用！ 线上一定要基于集群的方式做整体的部署验证，并做服务进程的守护及监控。\n创建一个只有一个节点的consul集群\n1 # docker run -id -expose=[8300,8301,8302,8500,8600] --restart always -p 18300:8300 -p 18301:8301 -p 18302:8302 -p 18500:8500 -p 18600:8600 --name server1 -e \u0026#39;CONSUL_LOCAL_CONFIG={\u0026#34;skip_leave_on_interrupt\u0026#34;: true}\u0026#39; consul agent -server -bootstrap-expect=1 -node=server1 -bind=0.0.0.0 -client=0.0.0.0 -ui -datacenter dc1 各参数说明：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 -expose：暴露出出来的端口，即consul启动所需的端口：8300,8301,8302,8500,8600 --restart：always表示容器挂了就自动重启 -p：建立宿主机与容器的端口映射 --name：容器名称 -e：环境变量，这里用于对consul进行配置 consul：这是consul镜像名，不是consul命令 agent：容器中执行的命令，各参数含义： -server：表示节点是server类型 -bootstrap-expect：表示集群中有几个server节点后开始选举leader，既然是单节点集群，那自然就是1了 -node：节点名称 -bind：集群内部通信地址，默认是0.0.0.0 -client：客户端地址，默认是127.0.0.1 -ui：启用consul的web页面管理 -datacenter：数据中心 测试验证：\n可通过web端访问，例如：http://192.10.192.109:18500/\n1 # curl localhost:18500 注册主机到 Consul\r例如：将某台虚机上的 node-exporter 注册到 consule.\n1 2 3 4 5 ## 格式 $ curl -X PUT -d \u0026#39;{\u0026#34;id\u0026#34;: \u0026#34;\u0026#39;${host_name}\u0026#39;\u0026#34;,\u0026#34;name\u0026#34;: \u0026#34;node-exporter\u0026#34;,\u0026#34;address\u0026#34;: \u0026#34;\u0026#39;${host_addr}\u0026#39;\u0026#34;,\u0026#34;port\u0026#34;:9100,\u0026#34;tags\u0026#34;: [\u0026#34;dam\u0026#34;],\u0026#34;checks\u0026#34;: [{\u0026#34;http\u0026#34;: \u0026#34;http://\u0026#39;${host_addr}\u0026#39;:9100/\u0026#34;,\u0026#34;interval\u0026#34;: \u0026#34;5s\u0026#34;}]}\u0026#39; http://192.10.192.109:18500/v1/agent/service/register ## 示例 $ curl -X PUT -d \u0026#39;{\u0026#34;id\u0026#34;: \u0026#34;sh-middler2\u0026#34;,\u0026#34;name\u0026#34;: \u0026#34;node-exporter\u0026#34;,\u0026#34;address\u0026#34;: \u0026#34;192.10.192.134\u0026#34;,\u0026#34;port\u0026#34;:9100,\u0026#34;tags\u0026#34;: [\u0026#34;middleware\u0026#34;],\u0026#34;checks\u0026#34;: [{\u0026#34;http\u0026#34;: \u0026#34;http://192.10.192.134:9100/metrics\u0026#34;,\u0026#34;interval\u0026#34;: \u0026#34;3s\u0026#34;}]}\u0026#39; http://192.10.192.109:18500/v1/agent/service/register 参数说明\n1 2 3 4 5 6 7 8 9 id : 注册ID 在consul中为唯一标识 name ：Service名称 address：自动注册绑定ip port：自动注册绑定端口 tags：注册标签，可多个 checks : 健康检查 http: 检查数据来源 interval: 检查时间间隔 http://192.10.192.109:18500/v1/agent/service/register consul注册接口 删除：\n1 2 3 4 5 ## 格式 $ curl -X PUT http://192.10.192.109:18500/v1/agent/service/deregister/${id} ## 示例 $ curl -X PUT http://192.10.192.109:18500/v1/agent/service/deregister/sh-middler2 Prometheus 配置 Consul 实现自动服务发现\r修改 prometheus 的 configmap 配置文件：prometheus-config.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - job_name: consul honor_labels: true metrics_path: /metrics scheme: http consul_sd_configs: #基于consul服务发现的配置 - server: 192.10.192.109:18500 #consul的监听地址 services: [] #匹配consul中所有的service relabel_configs: #relabel_configs下面都是重写标签相关配置 - source_labels: [\u0026#39;__meta_consul_tags\u0026#39;] #将__meta_consul_tags标签的至赋值给product target_label: \u0026#39;servername\u0026#39; - source_labels: [\u0026#39;__meta_consul_dc\u0026#39;] #将__meta_consul_dc的值赋值给idc target_label: \u0026#39;idc\u0026#39; - source_labels: [\u0026#39;__meta_consul_service\u0026#39;] regex: \u0026#34;consul\u0026#34; #匹配为\u0026#34;consul\u0026#34;的service action: drop #执行的动作为删除 按上面方法重载 Prometheus，打开 Prometheus 的 Target 页面，就会看到 上面定义的 mysql-exporter 任务\n1 curl -XPOST http://prometheus.kubernets.cn/-/reload 总结\r动态服务发现和监控：通过与Consul集成，Prometheus可以动态地维护其目标列表，确保在新服务上线时及时发现和监控它们。 可扩展性：自动服务发现使得扩展基础架构变得更加容易，无需担心监控数据的可用性和性能问题。 无缝集成：Consul作为服务注册中心，使得Prometheus可以与Consul生态系统中的其他工具进行无缝集成，提供完整的服务基础架构监控和管理解决方案。 自愈能力：自动服务发现意味着Prometheus可以自动检测服务基础架构的变化，并在实时调整监控目标列表，确保监控数据的连续性和高性能。 ","date":"2023-09-26T00:00:00Z","permalink":"/p/consul_based_prometheus_auto_discovery/","title":"基于Consul的Prometheus自动发现机制全解析"},{"content":"Docker 部署 acme.sh\rdocker-compose.yml，将 DNSPod API ID 和 Key 替换为实际值。\n1 2 3 4 5 6 7 8 9 10 11 12 services: acme-sh: image: neilpang/acme.sh:latest container_name: acme restart: always command: daemon environment: - TZ=Asia/Shanghai - DP_Id=****** # DNSPod API ID - DP_Key=**************** # DNSPod API Key volumes: - ./certs/:/acme.sh/ 配置 acme.sh\r1 2 3 4 5 6 # 启动 acme.sh docker compose up -d # 进入 acme.sh 容器 docker compose exec -it acme sh # 设置默认 CA acme.sh --set-default-ca --server letsencrypt 申请证书\r生成证书，将 www.example.com 替换为实际域名。证书会挂载到 ./certs/ 目录下。\n1 acme.sh --issue --dns dns_dp -d www.example.com ","date":"2023-09-18T00:00:00Z","image":"/p/acme_dnspod_ssl_certificate_guide/acme_dnspod_ssl_certificate_guide.png","permalink":"/p/acme_dnspod_ssl_certificate_guide/","title":"使用 ACME 在 DNSPod 上自动配置 SSL 证书指南"},{"content":"frp 是什么？\rfrp 是一款高性能的反向代理应用，专注于内网穿透。它支持多种协议，包括 TCP、UDP、HTTP、HTTPS 等，并且具备 P2P 通信功能。使用 frp，您可以安全、便捷地将内网服务暴露到公网，通过拥有公网 IP 的节点进行中转。\n为什么选择 frp？\r通过在具有公网 IP 的节点上部署 frp 服务端，您可以轻松地将内网服务穿透到公网，并享受以下专业特性：\n多种协议支持：客户端服务端通信支持 TCP、QUIC、KCP 和 Websocket 等多种协议。 TCP 连接流式复用：在单个连接上承载多个请求，减少连接建立时间，降低请求延迟。 代理组间的负载均衡。 端口复用：多个服务可以通过同一个服务端端口暴露。 P2P 通信：流量不必经过服务器中转，充分利用带宽资源。 客户端插件：提供多个原生支持的客户端插件，如静态文件查看、HTTPS/HTTP 协议转换、HTTP、SOCKS5 代理等，以便满足各种需求。 服务端插件系统：高度可扩展的服务端插件系统，便于根据自身需求进行功能扩展。 用户友好的 UI 页面：提供服务端和客户端的用户界面，使配置和监控变得更加方便。 frps\rfrp 的服务端，需要有公网 IP，开通12345端口。\ndocker-compose.yaml 1 2 3 4 5 6 7 8 9 10 11 services: frps: image: fatedier/frps:v0.61.0 container_name: frps restart: always working_dir: /etc/frps/ command: [\u0026#34;-c\u0026#34;, \u0026#34;./config.toml\u0026#34;] volumes: - /etc/localtime:/etc/localtime:ro - ./:/etc/frps/ network_mode: host config.toml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bindAddr = \u0026#34;0.0.0.0\u0026#34; bindPort = 12345 vhostHTTPPort = 12380 vhostHTTPSPort = 12343 webServer.addr = \u0026#34;0.0.0.0\u0026#34; webServer.port = 12375 webServer.user = \u0026#34;admin\u0026#34; webServer.password = \u0026#34;******\u0026#34; enablePrometheus = true log.to = \u0026#34;./logs/frps.log\u0026#34; log.level = \u0026#34;debug\u0026#34; log.maxDays = 7 log.disablePrintColor = false auth.method = \u0026#34;token\u0026#34; auth.token = \u0026#34;******\u0026#34; custom404Page = \u0026#34;./404.html\u0026#34; 404.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Not Found\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; body { font-family: Arial, sans-serif; text-align: center; margin-top: 100px; } h1 { font-size: 50px; margin-bottom: 10px; } p { font-size: 20px; margin-top: 0; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;404\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;Page not found\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; frpc\rdocker 部署\rdocker-compose.yaml 1 2 3 4 5 6 7 8 9 10 11 services: net-tunnel: image: \u0026#34;harbor.example.com/devops/kube-net-tunnel:standalone\u0026#34; container_name: \u0026#34;net-tunnel\u0026#34; restart: \u0026#34;always\u0026#34; network_mode: \u0026#34;bridge\u0026#34; working_dir: /etc/net-tunnel/ volumes: - \u0026#34;/etc/localtime:/etc/localtime:ro\u0026#34; - \u0026#34;./:/etc/net-tunnel/\u0026#34; command: [\u0026#34;-c\u0026#34;, \u0026#34;/etc/net-tunnel/config.toml\u0026#34;] config.toml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 user = \u0026#34;test\u0026#34; serverAddr = \u0026#34;*.*.*.*\u0026#34; serverPort = 12345 auth.method = \u0026#34;token\u0026#34; auth.token = \u0026#34;******\u0026#34; log.to = \u0026#34;./logs/frpc.log\u0026#34; log.level = \u0026#34;debug\u0026#34; log.maxDays = 3 log.disablePrintColor = false webServer.addr = \u0026#34;127.0.0.1\u0026#34; webServer.port = 12346 webServer.user = \u0026#34;admin\u0026#34; webServer.password = \u0026#34;******\u0026#34; [[proxies]] name = \u0026#34;admin_ui\u0026#34; type = \u0026#34;tcp\u0026#34; localIP = \u0026#34;127.0.0.1\u0026#34; localPort = 12346 remotePort = 12440 [[proxies]] name = \u0026#34;ssh\u0026#34; type = \u0026#34;tcp\u0026#34; localIP = \u0026#34;172.17.0.1\u0026#34; # Windows or MacOS: \u0026#34;host.docker.internal\u0026#34; localPort = 22 remotePort = 12441 K8s 部署\rkube-net-tunnel.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 apiVersion: v1 kind: ConfigMap metadata: name: kube-net-tunnel-config namespace: kube-system data: config.toml: | user=\u0026#34;test\u0026#34; serverAddr = \u0026#34;*.*.*.*\u0026#34; serverPort = 12345 auth.method = \u0026#34;token\u0026#34; auth.token = \u0026#34;******\u0026#34; log.to = \u0026#34;console\u0026#34; log.level = \u0026#34;debug\u0026#34; log.maxDays = 3 log.disablePrintColor = false webServer.addr = \u0026#34;127.0.0.1\u0026#34; webServer.port = 7400 webServer.user = \u0026#34;admin\u0026#34; webServer.password = \u0026#34;******\u0026#34; [[proxies]] name = \u0026#34;admin\u0026#34; type = \u0026#34;tcp\u0026#34; localIP = \u0026#34;127.0.0.1\u0026#34; localPort = 7400 remotePort = 12431 [[proxies]] name = \u0026#34;ssh\u0026#34; type = \u0026#34;tcp\u0026#34; localIP = \u0026#34;10.19.31.7\u0026#34; localPort = 22 remotePort = 12432 --- apiVersion: apps/v1 kind: Deployment metadata: name: kube-net-tunnel namespace: kube-system spec: replicas: 1 selector: matchLabels: app: kube-net-tunnel template: metadata: labels: app: kube-net-tunnel spec: containers: - name: kube-net-tunnel image: harbor.example.com/devops/kube-net-tunnel:standalone imagePullPolicy: IfNotPresent args: [\u0026#34;-c\u0026#34;, \u0026#34;/etc/kube-net-tunnel/config.toml\u0026#34;] volumeMounts: - name: config-volume mountPath: /etc/kube-net-tunnel/config.toml subPath: config.toml volumes: - name: config-volume configMap: defaultMode: 0755 name: kube-net-tunnel-config items: - key: config.toml path: config.toml ","date":"2023-07-09T00:00:00Z","permalink":"/p/frp_service_deployment_configuration/","title":"FRP 反向代理服务部署完整指南"},{"content":"参考\rhttps://www.cnblogs.com/lincappu/p/14926757.html\nFastDFS\rFastDFS的作者余庆在其 GitHub 上是这样描述的：“FastDFS is an open source high performance distributed file system. It\u0026rsquo;s major functions include: file storing, file syncing and file accessing (file uploading and file downloading), and it can resolve the high capacity and load balancing problem. FastDFS should meet the requirement of the website whose service based on files such as photo sharing site and video sharing site” ，意思说，FastDFS是一个开源的高性能分布式文件系统。其主要功能包括：文件存储、文件同步和文件访问（文件上传和文件下载），它可以解决高容量和负载平衡问题。FastDFS应满足基于照片共享站点和视频共享站点等文件的网站的服务要求。\nFastDFS 有两个角色：跟踪器（Tracker）和存储器（Storage）。Tracker 负责文件访问的调度和负载平衡。Storage 存储文件及其功能是文件管理，包括：文件存储、文件同步、提供文件访问接口。它还管理元数据，这些元数据是表示为文件的键值对的属性。Tracker 和 Storage 节点都可以由一台或多台服务器构成。这些服务器均可以随时增加或下线而不会影响线上服务，当然各个节点集群至少需要一台服务 Running。注意，其中 Tracker 集群中的所有服务器都是对等的（P2P），可以根据服务器的压力情况随时增加或减少。\n此外，官网还存储系统做了详尽的说明，为了支持大容量，Storage 节点采用了分卷（或分组）的组织方式。存储系统由一个或多个卷组成，卷与卷之间的文件是相互独立的，所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台 Storage 服务器组成，一个卷下的存储服务器中的文件都是相同的，卷中的多台存储服务器起到了冗余备份和负载均衡的作用。在卷中增加服务器时，同步已有的文件由系统自动完成，同步完成后，系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时，可以动态添加卷。只需要增加一台或多台服务器，并将它们配置为一个新的卷，这样就扩大了存储系统的容量。关于卷或组的概念暂且不要过多深入，后面的安装部署中会后详细的说明。\n在FastDFS中，文件的标识由两部分组成：卷名和文件名。\n环境说明\r操作系统：Centos Linux release 7.2.1511 系统磁盘： 274G 挂在磁盘：3.7T * 12 CPU：32 (Intel(R) Xeon(R) ) Memory：8G 架构设计\r工作方式：客户端向 tracker 发出请求，然后 tracker 从 storage 节点拿到源数据，返还给客户端，然后客户端根据源数据再去请求 storage 节点。\n概要精简\r1.核心系统只有两个角色，tracker server和storage server。\n2.所有跟踪器服务器都是对等的，不存在 Master-Slave 关系（图中 Tracker-leader 需另行讨论，目前只需要明白 tracker 是 P2P）。\n3.存储服务器采用分组方式，同组内存储服务器上的文件完全相同。\n4.不同组的 storage server 之间不会相互通信，相同组之间会进行同步。\n5.由 storage server 主动向 tracker server 报告状态信息，每个 tracker server 都会记录 storage server 的信息。\n6.如果开启了 trunk 功能，tracker 服务协调 storage 选举 trunk-server\n集群部署\r表1 软件列表及版本\n名称 说明 链接 CentOS 7.x （安装系统） libfastcommon FastDFS分离出的一些公用函数包 libfastcommon V1.0.39 FastDFS FastDFS本体 FastDFS V5.11 fastdfs-nginx-module FastDFS和nginx的关联模块，解决组内同步延迟问题 fastdfs-nginx-module V1.22 nginx nginx 1.12.2（CentOS 7 下YUM可以安装的最新版本） nginx 1.17.4 表2 服务器IP、服务分配、端口规划表\n名称 IP地址 应用服务 端口 A机器 10.58.10.136 tracker 22122 10.58.10.136 storage-group1 23000 10.58.10.136 storage-group2 33000 10.58.10.136 libfastcommon - 10.58.10.136 nginx 8888 10.58.10.136 fastdfs-nginx-module - B机器 10.58.10.137 tracker 22122 10.58.10.137 storage-group1 23000 10.58.10.137 storage-group3 43000 10.58.10.137 libfastcommon - 10.58.10.137 nginx 8888 10.58.10.137 fastdfs-nginx-module - C机器 10.58.10.138 tracker 22122 10.58.10.138 storage-group2 33000 10.58.10.138 storage-group23 43000 10.58.10.138 libfastcommon - 10.58.10.138 nginx 8888 10.58.10.138 fastdfs-nginx-module - 在安装之前必须说明：\n1.记得给下面用到的存储目录(日志、数据、pid文件等)授权可读可写权限；\n2.下面所有的配置文件中由于说明，加了注释，请务必在配置的时候把配置项后面以“#”开头的注释删掉。\n初始化环境\r1 2 # 安装编译环境 yum groups install Development Tools perl redhat-rpm-config.noarch gd-devel perl-devel perl-ExtUtils-Embed pcre-devel openssl openssl-devel gcc-c++ autoconf automake zlib-devel libxml2 libxml2-dev libxslt-devel GeoIP GeoIP-devel GeoIP-data gperftools-y 安装libfastcommon\r在A、B、C三台机器上分别执行下面的操作：\n1 2 3 4 tar -zxvf libfastcommon-1.0.39.tar.gz cd libfastcommon-1.0.39/ ./make.sh ./make.sh install libfastcommon 安装在了 /usr/lib64/libfastcommon.so。需要注意新老版本，新版本在安装之后自动会将 libfastcommon.so 通过软连接链接到 /usr/local/lib 目录下面。如果是老版本，则需要手动建立：\n1 ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so ln -s /usr/lib64/libfastcommon.so /usr/lib/libfastcommon.so 如果有 libfdfsclient.so，则也可以把libfdfsclient.so加入到 /usr/local/lib 中：\n1 ln -s /usr/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so ln -s /usr/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so !!! Note ：最好自己通过命令 ls | grep libfastcommon 在 /usr/lib/ 和 /usr/local/lib 查看确认是否链接成功。\n安装tracker\r在 A、B、C 三台机器上分别执行下面操作\n1 2 3 4 5 6 7 mkdir -p /data/fastdfs/tracker tar -zxvf fastdfs-5.11.tar.gz cd fastdfs-5.11/ ./make.sh ./make.sh install # 配置文件准备 cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf #tracker节点 修改tracker的配置文件\n1 2 3 4 5 vim /etc/fdfs/tracker.conf #需要修改的内容如下 max_connections=1024 #默认256，最大连接数 port=22122 # tracker 服务器端口（默认 22122,一般不修改） base_path=/data/fastdfs/tracker # 存储日志和数据的根目录 增加 tracker.service 服务，可使用 systemctl 执行服务的启动、重启、停止等操作，也可设置开机自动重启\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ #编辑启动文件 $ vim /usr/lib/systemd/system/fastdfs-tracker.service [Unit] Description=The FastDFS File server After=network.target remote-fs.target nss-lookup.target [Service] Type=forking ExecStart=/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start ExecStop=/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf stop ExecRestart=/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart [Install] WantedBy=multi-user.target 保存 /usr/lib/systemd/system/fastdfs-tracker.service 文件并退出 vim 编辑器，我们执行下面操作启动 Fastdfs Tracker 服务\n1 2 3 $ systemctl daemon-reload $ systemctl enable fastdfs-tracker.service $ systemctl start fastdfs-tracker.service tracker 服务启动结束后，我们通过下面命令查看端口是否正常：\n1 netstat -tulnp|grep 22122 #查看服务是否启动，端口是否打开 安装storage\r安装 tracker 的时候已经解压过了则可以不执行此步骤。\n1 2 3 4 $ tar -zxvf fastdfs-5.11.tar.gz $ cd fastdfs-5.11/ $ ./make.sh $ ./make.sh install A 机器(group1/group2)\r在 fastdfs-5.11 目录下面复制 storage 的配置文件（复制两份）\n1 2 3 4 5 sudo mkdir -p /data/fastdfs/storage/group1 sudo mkdir -p /data/fastdfs/storage/group2 sudo cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage-group1.conf #storage节点的group1组 sudo cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage-group2.conf #storage节点的group1组 sudo cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf #客户端文件，测试用 根据架构设计，我们依次修改上面的三个文件：\n修改 group1 的配置文件：/etc/fdfs/storage-group1.conf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ sudo vim /etc/fdfs/storage-group1.conf #需要修改的内容如下 group_name=group1 port=23000 # storage 服务端口（默认 23000） base_path=/data/fastdfs/storage/group1 # 数据和日志文件存储根目录 store_path_count=6 store_path0=/data01/fastdfs # group1 的第一个存储目录 store_path1=/data02/fastdfs # group1 的第二个存储目录 store_path2=/data03/fastdfs # group1 的第三个存储目录 store_path3=/data04/fastdfs # group1 的第四个存储目录 store_path4=/data05/fastdfs # group1 的第五个存储目录 store_path5=/data06/fastdfs # group1 的第六个存储目录 tracker_server=10.58.10.136:22122 # tracker 服务器IP和端口 tracker_server=10.58.10.137:22122 # tracker 服务器IP和端口 tracker_server=10.58.10.138:22122 # tracker 服务器IP和端口 http.server_port=8888 # http 访问文件的端口(默认 8888，看情况修改，和nginx 中保持一致) 修改 group2 的配置文件：/etc/fdfs/storage-group2.conf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ sudo vim /etc/fdfs/storage-group2.conf #需要修改的内容如下 group_name=group2 port=33000 # storage 服务端口（默认 23000，修改为 33000） base_path=/data/fastdfs/storage/group2 # 数据和日志文件存储根目录 store_path_count=6 store_path0=/data07/fastdfs # group2 的第一个存储目录 store_path1=/data08/fastdfs # group2 的第二个存储目录 store_path2=/data09/fastdfs # group2 的第三个存储目录 store_path3=/data10/fastdfs # group2 的第四个存储目录 store_path4=/data11/fastdfs # group2 的第五个存储目录 store_path5=/data12/fastdfs # group2 的第六个存储目录 tracker_server=10.58.10.136:22122 # tracker 服务器 IP 和端口 tracker_server=10.58.10.137:22122 # tracker 服务器 IP 和端口 tracker_server=10.58.10.138:22122 # tracker 服务器 IP 和端口 http.server_port=8888 # http 访问文件的端口(默认 8888，看情况修改，和nginx 中保持一致) 修改client的配置文件：/etc/fdfs/client.conf 1 2 3 4 5 6 $ sudo vim /etc/fdfs/client.conf #需要修改的内容如下 base_path=/data/fastdfs/client tracker_server=10.58.10.136:22122 # tracker 服务器 IP 和端口 tracker_server=10.58.10.137:22122 # tracker 服务器 IP 和端口 tracker_server=10.58.10.138:22122 # tracker 服务器 IP 和端口 修改好以上三个配置文件之后，我们制作启动 service fastdfs-storage-group1.service\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ vim /usr/lib/systemd/system/fastdfs-storage-group1.service # 编辑启动文件 [Unit] Description=The FastDFS File server After=network.target remote-fs.target nss-lookup.target [Service] Type=forking ExecStart=/usr/bin/fdfs_storaged /etc/fdfs/storage-group1.conf start ExecStop=/usr/bin/fdfs_storaged /etc/fdfs/storage-group1.conf stop ExecRestart=/usr/bin/fdfs_storaged /etc/fdfs/storage-group1.conf restart [Install] WantedBy=multi-user.target fastdfs-storage-group2.service\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ vim /usr/lib/systemd/system/fastdfs-storage-group2.service # 编辑启动文件 [Unit] Description=The FastDFS File server After=network.target remote-fs.target nss-lookup.target [Service] Type=forking ExecStart=/usr/bin/fdfs_storaged /etc/fdfs/storage-group2.conf start ExecStop=/usr/bin/fdfs_storaged /etc/fdfs/storage-group2.conf stop ExecRestart=/usr/bin/fdfs_storaged /etc/fdfs/storage-group2.conf restart [Install] WantedBy=multi-user.target Storage 的 service 制作好只好，我们启动 Storage 的两个服务 1 2 3 $ systemctl daemon-reload $ systemctl enable fastdfs-storage-group1.service $ systemctl start fastdfs-storage-group1.service 启动过程可能因为某些权限或配置书写错误的问题，会出现启动失败的情况，可通过 systemctl status fastdfs-storage-group1.service 查看该服务的状态，同时结合logs（日志文件在 /data/fastdfs/storage/group1/logs/目录下面）排查，可以快速定位问题。如果出现一些异常情况，也可以看看后面的采坑部分，看看有没有期望的结果。\n1 netstat -tulnp #查看服务是否启动，端口是否打开（23000，33000） 服务起来之后，我们可以查看Fastdfs的集群状态： 1 2 #查看集群状态 $ fdfs_monitor /etc/fdfs/storage-group1.conf list ??? note \u0026ldquo;信息\u0026rdquo;\n1 2 3 4 5 6 7 控制台打印出如下信息，说明成功了： [2018-11-06 00:00:00] DEBUG - base_path=/data/fastdfs/storage/group1, connect_timeout=30, network_timeout=60, tracker_server_count=2, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0 server_count=3, server_index=0 tracker server is 10.58.10.136:22122,10.58.10.137:22122,10.58.10.138:22122 group count: 2 Group 1: ... 通过客户端测试上传文件 1 2 #保存后测试,返回 ID 表示成功 如：group1/M00/00/00/xx.txt $ fdfs_upload_file /etc/fdfs/client.conf test.txt B 机器(group1/group3)\r配置过程与 A 机器类似，需要修改以下几个点：\n创建目录及复制配置文件 1 2 3 4 5 $ sudo mkdir -p /data/fastdfs/storage/group1 $ sudo mkdir -p /data/fastdfs/storage/group3 $ sudo cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage-group1.conf #storage节点的group1组 $ sudo cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage-group3.conf #storage节点的group3组 $ sudo cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf #客户端文件，测试用 修改 group3 的配置文件/etc/fdfs/storage-group3.conf，group1 的配置内容和 A 的保持一致。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ sudo vim /etc/fdfs/storage-group3.conf #需要修改的内容如下 group_name=group3 port=43000 # storage服务端口（默认23000，修改为43000） base_path=/data/fastdfs/storage/group3 # 数据和日志文件存储根目录 store_path_count=6 store_path0=/data07/fastdfs # group3的第一个存储目录 store_path1=/data08/fastdfs # group3的第二个存储目录 store_path2=/data09/fastdfs # group3的第三个存储目录 store_path3=/data10/fastdfs # group3的第四个存储目录 store_path4=/data11/fastdfs # group3的第五个存储目录 store_path5=/data12/fastdfs # group3的第六个存储目录 tracker_server=10.58.10.136:22122 # tracker服务器IP和端口 tracker_server=10.58.10.137:22122 # tracker服务器IP和端口 tracker_server=10.58.10.138:22122 # tracker服务器IP和端口 http.server_port=8888 # http访问文件的端口(默认8888,看情况修改,和nginx中保持一致) client 的配置文件和 A 机器一致，此处不做重复。 制作启动 group3 的 service，fastdfs-storage-group1.service和 A 机器一致，复制即可。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ vim /usr/lib/systemd/system/fastdfs-storage-group3.service # 编辑启动文件 [Unit] Description=The FastDFS File server After=network.target remote-fs.target nss-lookup.target [Service] Type=forking ExecStart=/usr/bin/fdfs_storaged /etc/fdfs/storage-group3.conf start ExecStop=/usr/bin/fdfs_storaged /etc/fdfs/storage-group3.conf stop ExecRestart=/usr/bin/fdfs_storaged /etc/fdfs/storage-group3.conf restart [Install] WantedBy=multi-user.target 执行启动脚本，直到 fastdfs-storage-group1.service 和 fastdfs-storage-group3.service 服务起来即可。 C 机器(group2/group3)\r配置过程与 A 机器类似，需要修改以下几个点：\n创建目录及复制配置文件。 1 2 3 4 5 $ sudo mkdir -p /data/fastdfs/storage/group2 $ sudo mkdir -p /data/fastdfs/storage/group3 $ sudo cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage-group2.conf #storage节点的group1组 $ sudo cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage-group3.conf #storage节点的group3组 $ sudo cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf #客户端文件，测试用 修改 group2 的配置文件/etc/fdfs/storage-group2.conf，group3 的配置内容和 B 机器的保持一致。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ sudo vim /etc/fdfs/storage-group2.conf #需要修改的内容如下 group_name=group2 port=33000 # storage服务端口（默认23000，修改为33000） base_path=/data/fastdfs/storage/group2 # 数据和日志文件存储根目录 store_path_count=6 store_path0=/data01/fastdfs # group2的第一个存储目录 store_path1=/data02/fastdfs # group2的第二个存储目录 store_path2=/data03/fastdfs # group2的第三个存储目录 store_path3=/data04/fastdfs # group2的第四个存储目录 store_path4=/data05/fastdfs # group2的第五个存储目录 store_path5=/data06/fastdfs # group2的第六个存储目录 tracker_server=10.58.10.136:22122 # tracker服务器IP和端口 tracker_server=10.58.10.137:22122 # tracker服务器IP和端口 tracker_server=10.58.10.138:22122 # tracker服务器IP和端口 http.server_port=8888 # http访问文件的端口(默认8888,看情况修改,和nginx中保持一致) client 的配置文件和 A 机器一致，此处不做重复。 制作启动 group2 和 group3 的 service，上面已有，直接复制即可。 执行启动脚本，直到fastdfs-storage-group2.service和fastdfs-storage-group3.service服务起来即可。 安装nginx和fastdfs的nginx模块\rstep1 在 fastdfs 的目录中，将 http.conf 和 mime.types 文件拷贝到 /etc/fdfs 目录下面，支持 nginx 访问 storage 服务\n1 2 3 # 在三台机器上都需要执行 $ cp ./conf/http.conf /etc/fdfs/ #供 nginx 访问使用 $ cp ./conf/mime.types /etc/fdfs/ #供 nginx 访问使用 step2 安装 fastdfs 的 nginx 模块：\n1 2 3 # 在三台机器上都需要执行 $ tar -zxvf V1.20.tar.gz $ cp fastdfs-nginx-module-1.20/src/mod_fastdfs.conf /etc/fdfs/mod_fastdfs.conf step3 然后修改 fastdfs-nginx-module-1.20/src/config 文件，找到 ngx_module_incs 和 CORE_INCS 两处，修改如下：\n1 2 ngx_module_incs=”/usr/include/fastdfs /usr/include/fastcommon/” CORE_INCS=”$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/” 如果不修改，在编译 nginx 的时候，会出这种错误：/usr/include/fastdfs/fdfs_define.h:15:27: fatal error: common_define.h: No such file or directory。\nstep4 之后我们解压 nginx，安装 nginx 服务：\n1 2 3 4 5 $ tar -zxvf nginx-1.12.2.tar.gz $ cd nginx-1.12.2 $ ./configure --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_auth_request_module --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-google_perftools_module --with-debug --with-cc-opt=\u0026#39;-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic\u0026#39; --with-ld-opt=\u0026#39;-Wl,-z,relro -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E\u0026#39; --add-module=${YOUR_PATH}/fastdfs-nginx-module-1.20/src $ make $ make install 注意，上面 ${YOUR_PATH} 改为 fastdfs-nginx-module-1.20 的父目录，保证路径正确。\n修改A机器 /etc/fdfs/mod_fastdfs.conf 配置文件： 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 connect_timeout=2 network_timeout=30 base_path=/data/fastdfs/ngx_mod load_fdfs_parameters_from_tracker=true storage_sync_file_max_delay = 86400 use_storage_id = false storage_ids_filename = storage_ids.conf tracker_server=10.58.10.136:22122 # tracker服务器IP和端口 tracker_server=10.58.10.137:22122 # tracker服务器IP和端口 tracker_server=10.58.10.138:22122 # tracker服务器IP和端口 group_name=group1/group2 # 全局 url_have_group_name = true log_level=info log_filename= response_mode=proxy if_alias_prefix= flv_support = true flv_extension = flv group_count = 2 [group1] group_name=group1 #group中 storage_server_port=23000 store_path_count=6 store_path0=/data01/fastdfs store_path1=/data02/fastdfs store_path2=/data03/fastdfs store_path3=/data04/fastdfs store_path4=/data05/fastdfs store_path5=/data06/fastdfs [group2] group_name=group2 storage_server_port=33000 store_path_count=6 store_path0=/data07/fastdfs store_path1=/data08/fastdfs store_path2=/data09/fastdfs store_path3=/data10/fastdfs store_path4=/data11/fastdfs store_path5=/data12/fastdfs 配置 nginx 的配置文件，以便访问 storage 1 $ sudo vim /etc/nginx/nginx.conf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 user nginx; worker_prosesses on; worker_rlimit_nofile 65535; error_log /var/log/nginx/error.log; pid /run/nginx.pid; include /usr/share/nginx/modules/*.conf; events { worker_connections 65535; use epoll; accept_mutex off; } http { log_format main \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; gzip on; server_names_bucket_hash_size 128; client_header_buffer_size 32k; larger_client_header_buffers 4 32k; client_max_body_size 300m; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Connection \u0026#39;\u0026#39;; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header HOST $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connet_timeout 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 16k; proxy_buffers 8 64k; proxy_busy_buffers_size 128k; proxy_temp_file_write_size 128k; proxy_cache_path /data/fastdfs/cache/nginx/proxy_cache levels=1:2 keys_zone=http-cache:200m max_size=1g inactive=30d; proxy_temp_path /data/fastdfs/cache/nginx/proxy_cache/temp; include /etc/nginx/mime.types; default_type application/octet-stream; include /etc/nginx/conf.d/*.conf; server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /usr/share/nginx/html; # Load configuration files for the default server block. #include /etc/nginx/default.d/*.conf; location ~ ^/ok(\\..*)?$ { return 200 \u0026#34;OK\u0026#34;; } location /nginx { stub_status on; } location /healthcheck { check_status on; } location ^~ /group1/ { proxy_next_upstream http_502 http_504 error timeout invalid_header; proxy_cache http-cache; proxy_cache_valid 200 304 12h; proxy_cache_key $uri$is_args$args; add_header \u0026#39;Access-Control-Allow-Origin\u0026#39; $http_origin; add_header \u0026#39;Access-Control-Allow-Credentials\u0026#39; \u0026#39;true\u0026#39;; add_header \u0026#34;Access-Control-Allow-Methods\u0026#34; \u0026#34;GET, POST, HEAD, PUT, DELETE, OPTIONS, PATCH\u0026#34;; add_header \u0026#34;Access-Control-Allow-Headers\u0026#34; \u0026#34;Origin, No-Cache, Authorization, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type\u0026#34;; if ($request_method = \u0026#39;OPTIONS\u0026#39;) { return 200 \u0026#39;OK\u0026#39;; } proxy_pass http://fdfs_group1; expire 30d; } location ^~ /group2/ { proxy_next_upstream http_502 http_504 error timeout invalid_header; proxy_cache http-cache; proxy_cache_valid 200 304 12h; proxy_cache_key $uri$is_args$args; add_header \u0026#39;Access-Control-Allow-Origin\u0026#39; $http_origin; add_header \u0026#39;Access-Control-Allow-Credentials\u0026#39; \u0026#39;true\u0026#39;; add_header \u0026#34;Access-Control-Allow-Methods\u0026#34; \u0026#34;GET, POST, HEAD, PUT, DELETE, OPTIONS, PATCH\u0026#34;; add_header \u0026#34;Access-Control-Allow-Headers\u0026#34; \u0026#34;Origin, No-Cache, Authorization, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type\u0026#34;; if ($request_method = \u0026#39;OPTIONS\u0026#39;) { return 200 \u0026#39;OK\u0026#39;; } proxy_pass http://fdfs_group2; expire 30d; } location ^~ /group3/ { proxy_next_upstream http_502 http_504 error timeout invalid_header; proxy_cache http-cache; proxy_cache_valid 200 304 12h; proxy_cache_key $uri$is_args$args; add_header \u0026#39;Access-Control-Allow-Origin\u0026#39; $http_origin; add_header \u0026#39;Access-Control-Allow-Credentials\u0026#39; \u0026#39;true\u0026#39;; add_header \u0026#34;Access-Control-Allow-Methods\u0026#34; \u0026#34;GET, POST, HEAD, PUT, DELETE, OPTIONS, PATCH\u0026#34;; add_header \u0026#34;Access-Control-Allow-Headers\u0026#34; \u0026#34;Origin, No-Cache, Authorization, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type\u0026#34;; if ($request_method = \u0026#39;OPTIONS\u0026#39;) { return 200 \u0026#39;OK\u0026#39;; } proxy_pass http://fdfs_group3; expire 30d; } location ~/purge(/.*) { allow 127.0.0.1; allow 192.168.1.0/24; allow 10.58.1.0/24; deny all; proxy_cache_purge http-cache $1$is_args$args; } } server { listen 8888; server_name localhost; location /ok.htm { return 200 \u0026#34;OK\u0026#34;; } location ~/group[0-9]/ { ngx_fastdfs_module; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } upstream fdfs_group1 { server 10.58.10.136:8888 max_fails=0; server 10.58.10.137:8888 max_fails=0; keepalive 10240; check interval=2000 rise=2 fall=3 timeout=1000 type=http default_down=false; check_http_send \u0026#34;GET /ok.htm HTTP/1.0\\r\\nConnection:keep-alive\\r\\n\\r\\n\u0026#34;; check_keepalive_requests 100; } upstream fdfs_group2 { server 10.58.10.136:8888 max_fails=0; server 10.58.10.138:8888 max_fails=0; keepalive 10240; check interval=2000 rise=2 fall=3 timeout=1000 type=http default_down=false; check_http_send \u0026#34;GET /ok.htm HTTP/1.0\\r\\nConnection:keep-alive\\r\\n\\r\\n\u0026#34;; check_keepalive_requests 100; } upstream fdfs_group3 { server 10.58.10.137:8888 max_fails=0; server 10.58.10.138:8888 max_fails=0; keepalive 10240; check interval=2000 rise=2 fall=3 timeout=1000 type=http default_down=false; check_http_send \u0026#34;GET /ok.htm HTTP/1.0\\r\\nConnection:keep-alive\\r\\n\\r\\n\u0026#34;; check_keepalive_requests 100; } } 启动 nginx 服务： sudo nginx -c /etc/nginx/nginx.conf\n访问http://localhost/ok.htm看看是\u0026hellip; 200 的状态,并有 OK 内容。如果启动失败，可以查看nginx的日志，日志在/var/log/nginx/error.log文件中，这个也是 nginx 在 Centos 中的默认错误日志路径，如果修改过 error_log，那么在 error_log 指定的文件中查看错误信息。\nB 和 C 机器的配置基本一样，nginx 的配置文件完全一样，唯一需要修改的是/etc/fdfs/mod_fastdfs.conf，在 group_name 中根据节点中 storage 的配置修改对应的名字即可。但是需要注意的是方括号[groupX]中X一定是顺序递增的，从1开始。 问题总结\r遇到问题，第一，我们通过 Console 打印的信息排查服务启动失败的原因；第二，通过日志排查。大多数时候，日志是最有效的排查方式，没有之一。tracker 或 storage 服务的日志存放在其服务对应配置文件中 base_path 配置项指定路径的 logs 目录下面。 nginx的日志，如果开启了自己指定日志路径，那么就是自己指定的路径下查找。如果默认，在 Centos 下面大多数在 /var/log/nginx/ 目录下面。当然，在这种环境中我们排查的不单单是 nginx，还有 fastdfs 的对 nginx 的扩展模块，这时候，要考虑 fastdfs-nginx-module 的日志的排查，其日志存放路径为 /etc/fdfs/mod_fastdfs.conf 配置中 base_path 指定的路径。\n集群中多个 group 配置端口 每个 group 都需要独立一个 storage 服务，因此同一台主机上 storage group 的端口不能冲突，这里我把 group1 的规划为 23000，group2 的规划为 33000，group3 的规划为43000.\n相同名称的 group 不能在同一台机器上，因为 tracker 调度时，相同名称的 group 之间会自动进行同步，需要相同的端口号。故此，两台主机上统一名称的 group 必须保证 storage 的端口一致。\n集群中多个group存储路径的配置 同一主机不同的 group 存储路径不要放在一起，需要分开配置。\n不同主机相同的 group 存储路径可以不一致，但是数量必须保证相同，大小也应该基本一致。\n同一节点多个 storage 的配置 在同一个机器上，我们部署了两个 group，三台机器组成的集群，每个 group 在另外一个节点中保存一份副本。使用命令systemctl start fastdfs-storage-groupx.service启动的时候，另外一个 group 无法启动（groupx 为 group1、group2、group3）。一直显示为 Loaded: loaded (/usr/lib/systemd/system/fastdfs-storage-group1.service; enabled; vender preset: disabled)，直到最好失败 Active: inactive 变为 Active: exited (下图为tracker的，storage 的问题类似)。\n当时的解决方法：每个 storage 启动时都需要 systemctl daemon-reload 一下，然后启动即可。 假如要启动A机器中的 fastdfs-storage-group1.service 和 fastdfs-storage-group2.service，则需要执行如下操作：\n先启动 fastdfs-storage-group1.service 1 2 3 sudo systemctl daemon-reload sudo systemctl enable fastdfs-storage-group1.service sudo systemctl start fastdfs-storage-group1.service 观察 fastdfs-storage-group1.service 的状态： 1 systemctl status fastdfs-storage-group1.service 当看到 active 为 running 的时候，说明启动成功了，然后启动 fastdfs-storage-group2.service；否则没启动成功，查看日志寻找问题即可： 1 2 3 sudo systemctl daemon-reload sudo systemctl enable fastdfs-storage-group2.service sudo systemctl start fastdfs-storage-group3.service 观察 fastdfs-storage-group2.service 的状态，直到起来为止。 可能会出现 Unkown lvalue \u0026lsquo;ExecRestart\u0026rsquo; in section \u0026lsquo;Service\u0026rsquo; 的警告日志，目前未找到解决方法，当时执行了 yum install systemd-* 的命令，也没能解决这个问题（有知道解决方案的朋友麻烦告知一下）。 fastdfs的nginx模块配置 在所有 storage 机器上，修改 /etc/fdfs/mod_fastdfs.conf 时，根据不同的 storage 中的 group 修改 group_name 中的值，但是需要注意两个配置项目，下面就 group2 和 group3 为例说明一下：\n全局的 group_name 全局配置中的 group_name 必须用”/“分割，值为局部中的 group_name 的值。比如：\n1 group_name=group2/group3。 group 中的 group_name group 标识一定是从 group1 开始，依次为 group2，这里的标识就是方括号中的字符串，类似 mysql 配置文件中的 [Mysql]。\n1 2 3 4 5 6 7 8 9 10 11 12 13 group_name=group2/group3 # 全局 ... group_count = 2 [group1] # 一定要注意这个地方 group_name=grou2 #group中 storage_server_port=33000 store_path_count=6 ...... [group2] # 还有这个地方，都是有序的 group_name=group3 storage_server_port=43000 store_path_count=6 ...... ","date":"2023-07-04T00:00:00Z","permalink":"/p/fastdfs_cluster_deployment/","title":"FastDFS 集群部署完整指南"},{"content":"问题\r我在ubuntu22.04的容器里面运行apt update的时候出现了以下报错\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 [root@VM-16-9-centos docker-kubuntu]# docker run --rm -it ubuntu:22.04 bash root@8ac245b487e6:/# apt update Get:1 http://security.ubuntu.com/ubuntu jammy-security InRelease [110 kB] Get:2 http://archive.ubuntu.com/ubuntu jammy InRelease [270 kB] Err:1 http://security.ubuntu.com/ubuntu jammy-security InRelease The following signatures couldn\u0026#39;t be verified because the public key is not available: NO_PUBKEY 871920D1991BC93C Err:2 http://archive.ubuntu.com/ubuntu jammy InRelease The following signatures couldn\u0026#39;t be verified because the public key is not available: NO_PUBKEY 871920D1991BC93C Get:3 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [109 kB] Err:3 http://archive.ubuntu.com/ubuntu jammy-updates InRelease The following signatures couldn\u0026#39;t be verified because the public key is not available: NO_PUBKEY 871920D1991BC93C Get:4 http://archive.ubuntu.com/ubuntu jammy-backports InRelease [90.7 kB] Err:4 http://archive.ubuntu.com/ubuntu jammy-backports InRelease The following signatures couldn\u0026#39;t be verified because the public key is not available: NO_PUBKEY 871920D1991BC93C Reading package lists... Done W: http://security.ubuntu.com/ubuntu/dists/jammy-security/InRelease: The key(s) in the keyring /etc/apt/trusted.gpg.d/ubuntu-keyring-2012-cdimage.gpg are ignored as the file is not readable by user \u0026#39;_apt\u0026#39; executing apt-key. W: http://security.ubuntu.com/ubuntu/dists/jammy-security/InRelease: The key(s) in the keyring /etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg are ignored as the file is not readable by user \u0026#39;_apt\u0026#39; executing apt-key. W: GPG error: http://security.ubuntu.com/ubuntu jammy-security InRelease: The following signatures couldn\u0026#39;t be verified because the public key is not available: NO_PUBKEY 871920D1991BC93C E: The repository \u0026#39;http://security.ubuntu.com/ubuntu jammy-security InRelease\u0026#39; is not signed. N: Updating from such a repository can\u0026#39;t be done securely, and is therefore disabled by default. N: See apt-secure(8) manpage for repository creation and user configuration details. W: http://archive.ubuntu.com/ubuntu/dists/jammy/InRelease: The key(s) in the keyring /etc/apt/trusted.gpg.d/ubuntu-keyring-2012-cdimage.gpg are ignored as the file is not readable by user \u0026#39;_apt\u0026#39; executing apt-key. W: http://archive.ubuntu.com/ubuntu/dists/jammy/InRelease: The key(s) in the keyring /etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg are ignored as the file is not readable by user \u0026#39;_apt\u0026#39; executing apt-key. W: GPG error: http://archive.ubuntu.com/ubuntu jammy InRelease: The following signatures couldn\u0026#39;t be verified because the public key is not available: NO_PUBKEY 871920D1991BC93C E: The repository \u0026#39;http://archive.ubuntu.com/ubuntu jammy InRelease\u0026#39; is not signed. N: Updating from such a repository can\u0026#39;t be done securely, and is therefore disabled by default. N: See apt-secure(8) manpage for repository creation and user configuration details. W: http://archive.ubuntu.com/ubuntu/dists/jammy-updates/InRelease: The key(s) in the keyring /etc/apt/trusted.gpg.d/ubuntu-keyring-2012-cdimage.gpg are ignored as the file is not readable by user \u0026#39;_apt\u0026#39; executing apt-key. W: http://archive.ubuntu.com/ubuntu/dists/jammy-updates/InRelease: The key(s) in the keyring /etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg are ignored as the file is not readable by user \u0026#39;_apt\u0026#39; executing apt-key. W: GPG error: http://archive.ubuntu.com/ubuntu jammy-updates InRelease: The following signatures couldn\u0026#39;t be verified because the public key is not available: NO_PUBKEY 871920D1991BC93C E: The repository \u0026#39;http://archive.ubuntu.com/ubuntu jammy-updates InRelease\u0026#39; is not signed. N: Updating from such a repository can\u0026#39;t be done securely, and is therefore disabled by default. N: See apt-secure(8) manpage for repository creation and user configuration details. W: http://archive.ubuntu.com/ubuntu/dists/jammy-backports/InRelease: The key(s) in the keyring /etc/apt/trusted.gpg.d/ubuntu-keyring-2012-cdimage.gpg are ignored as the file is not readable by user \u0026#39;_apt\u0026#39; executing apt-key. W: http://archive.ubuntu.com/ubuntu/dists/jammy-backports/InRelease: The key(s) in the keyring /etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg are ignored as the file is not readable by user \u0026#39;_apt\u0026#39; executing apt-key. W: GPG error: http://archive.ubuntu.com/ubuntu jammy-backports InRelease: The following signatures couldn\u0026#39;t be verified because the public key is not available: NO_PUBKEY 871920D1991BC93C E: The repository \u0026#39;http://archive.ubuntu.com/ubuntu jammy-backports InRelease\u0026#39; is not signed. N: Updating from such a repository can\u0026#39;t be done securely, and is therefore disabled by default. N: See apt-secure(8) manpage for repository creation and user configuration details. E: Problem executing scripts APT::Update::Post-Invoke \u0026#39;rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\u0026#39; E: Sub-process returned an error code 原因\r经过查询，发现是ubuntu21.10和fedora35开始使用glibc2.34甚至更高的版本。在glibc2.34版本里面，开始使用一个名为clone3的系统调用。通常情况下，容器里面所有的系统调用都会被docker捕获，然后docker决定如何处理它们。如果docker中没有为特定系统调用指定策略，则默认的策略会通知容器这边\u0026quot;Permission Denied\u0026quot;。但是，如果 Glibc 收到此错误，它不会回退。它仅在收到响应“此系统调用不可用”时才执行此操作。\n解决\r办法一\r运行容器的时候，加上这个参数来绕过docker系统调用限制\n1 --security-opt seccomp=unconfined 不过这会有很大的问题，一个是你的容器将变得不安全，另一个是这些参数在构建镜像的时候是不可用的。所以，请参考办法二\n办法二\r将docker升级到20.10.8以上的版本（\u0026gt; 20.10.8）\n由于生产环境调整docker版本不是一件容易的事情，所以生产环境在构建镜像时候要避免使用ubuntu21.10和fedora35以更高版本的镜像，以及使用它们作为基础镜像的其他镜像。目前官方大部分镜像基于debian，后续要确认debian系列镜像是否受此影响\n办法三\r升级runc。\nhttps://github.com/opencontainers/runc/releases/\n升级前使用docker version查看runc命令\n1.选择1.0.0-rc95版本，下载runc.amd64\n2. 上传服务器，修改名字并赋予权限\n1 mv runc.amd64 runc \u0026amp;\u0026amp; chmod +x runc 3.备份原有的runc\n1 2 which runc mv 4.停止docker\n1 systemctl stop docker 5.替换新版本runc\n1 cp runc /usr/bin/runc 6. 启动docker\n1 systemctl start docker 7.检查runc是否升级成功\n1 docker version ","date":"2023-06-05T00:00:00Z","permalink":"/p/docker_apt_update_gpg_issue/","title":"Docker中apt update的GPG签名问题及解决方法"},{"content":"所需文件下载：\n1 2 wget https://minio-console.coderkang.top/files/atlassian-agent.jar wget https://minio-console.coderkang.top/files/mysql-connector-j-8.3.0.jar docker-compose.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 version: \u0026#39;3.8\u0026#39; name: atlassian services: confluence: image: atlassian/confluence-server:8.5.2 container_name: atlassian-confluence restart: always environment: - TZ=Asia/Shanghai - JVM_MINIMUM_MEMORY=4096m - JVM_MAXIMUM_MEMORY=8192m - ATL_DB_TYPE=mysql - ATL_JDBC_URL=jdbc:mysql://db:3306/confluence?sessionVariables=transaction_isolation=\u0026#39;READ-COMMITTED\u0026#39; - ATL_JDBC_USER=root - ATL_JDBC_PASSWORD=****** - JAVA_OPTS=\u0026#39;-javaagent:/opt/atlassian-agent.jar\u0026#39; ports: - 38090:8090 - 38091:8091 volumes: - ./application-data/:/var/atlassian/application-data/confluence/ # - ./setenv.sh:/opt/atlassian/confluence/bin/setenv.sh - ./atlassian-agent.jar:/opt/atlassian-agent.jar - ./mysql-connector-j-8.3.0.jar:/opt/atlassian/confluence/confluence/WEB-INF/lib/mysql-connector-j-8.3.0.jar healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;curl\u0026#34;, \u0026#34;-f\u0026#34;, \u0026#34;http://localhost:8090\u0026#34;] interval: 10s timeout: 5s retries: 5 start_period: 30s depends_on: db: condition: service_healthy db: image: mysql:latest container_name: atlassian-db restart: always environment: MYSQL_ROOT_PASSWORD: ****** MYSQL_DATABASE: confluence command: --character-set-server=utf8mb4 --collation-server=utf8mb4_bin volumes: - ./application-db/data/:/var/lib/mysql/ - ./application-db/conf.d/:/etc/mysql/conf.d/ healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;mysqladmin\u0026#34;, \u0026#34;ping\u0026#34;, \u0026#34;-h\u0026#34;, \u0026#34;localhost\u0026#34;, \u0026#34;-u\u0026#34;, \u0026#34;root\u0026#34;, \u0026#34;-p******\u0026#34;] interval: 10s timeout: 5s retries: 5 start_period: 30s 1 2 docker compose up -d # 启动时间可能会比较长，因为要等待 MySQL 服务完全启动 访问页面获取 Server ID。\n1 2 3 docker compose exec -it confluence java -jar /opt/atlassian-agent.jar -m \u0026lt;邮箱\u0026gt; -n \u0026lt;用户名\u0026gt; -o \u0026lt;组织名称\u0026gt; -p conf -s \u0026#39;\u0026lt;Server ID\u0026gt;\u0026#39; # docker compose exec -it confluence java -jar /opt/atlassian-agent.jar -m CoderKang@hotmail.com -n CoderKang -o NiKo -p conf -s \u0026#39;BHE8-N86V-SW29-TDDO\u0026#39; 输入后点及 Next，等待……\n📌\r重要\r安装过程中遇到问题，down 掉容器后，最好删除 application-db 和 application-data 目录后重建容器\n","date":"2023-03-22T00:00:00Z","permalink":"/p/docker_confluence_setup_guide/","title":"使用Docker搭建Confluence指南"},{"content":"K8S 生命周期和探针\r1. Pod 的生命周期\r📝\r备注\rPod 遵循预定义的生命周期，起始于 Pending 阶段， 如果至少其中有一个主要容器正常启动，则进入 Running，之后取决于 Pod 中是否有容器以失败状态结束而进入 Succeeded 或者 Failed 阶段。\n当一个 Pod 被删除时，执行一些 kubectl 命令会展示这个 Pod 的状态为 Terminating（终止）。 这个 Terminating 状态并不是 Pod 阶段之一。\n描述 Pending（悬决） Pod 已被 Kubernetes 系统接受，但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。 Running（运行中） Pod 已经绑定到了某个节点，Pod 中所有的容器都已被创建。至少有一个容器仍在运行，或者正处于启动或重启状态。 Succeeded（成功） Pod 中的所有容器都已成功终止，并且不会再重启。 Failed（失败） Pod 中的所有容器都已终止，并且至少有一个容器是因为失败终止。也就是说，容器以非 0 状态退出或者被系统终止，且未被设置为自动重启。 Unknown（未知） 因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。 2. 容器的状态\rKubernetes 会跟踪 Pod 中每个容器的状态，就像它跟踪 Pod 生命周期一样。\n一旦调度器将 Pod 分派给某个节点，kubelet 就通过容器运行时开始为 Pod 创建容器。容器的状态有三种：Waiting（等待）、Running（运行中）和 Terminated（已终止）。要检查 Pod 中容器的状态，可以使用 kubectl describe pod \u0026lt;pod 名称\u0026gt;。 其输出中包含 Pod 中每个容器的状态。\n每种状态都有特定的含义：\n2.1 Waiting （等待）\r如果容器并不处在 Running 或 Terminated 状态之一，它就处在 Waiting 状态。 处于 Waiting 状态的容器仍在运行它完成启动所需要的操作：例如， 从某个容器镜像仓库拉取容器镜像，或者向容器传递并应用 ConfigMap、Secret 数据等等。\n2.2 Running（运行中）\rRunning 状态表明容器正在执行状态并且没有问题发生。\n2.3 Terminated（已终止）\r处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。\n3. Pod 异常场景\rPod 在其生命周期的许多时间点可能发生不同的异常，按照 Pod 容器是否运行为标志点，可以将异常场景大致分为两类：\n在 Pod 进行调度并创建容器过程中发生异常，此时 Pod 将卡在 Pending 阶段。 Pod 容器运行中发生异常，此时 Pod 按照具体场景处在不同阶段。 4. 容器探针\rprobe 是由 kubelet 对容器执行的定期诊断。 要执行诊断，kubelet 既可以在容器内执行代码，也可以发出一个网络请求。\n配置存活、就绪和启动探针 | Kubernetes\n4.1 存活探针livenessProbe\r4.1.1 什么是存活态探针?\r存活探针决定何时重启容器。 例如，当应用在运行但无法取得进展时，存活探针可以捕获这类死锁。\n如果一个容器的存活探针失败多次，kubelet 将重启该容器。如果存活态探测失败，则 kubelet 会杀死容器， 并且容器将根据其重启策略决定后续动作。如果容器不提供存活探针， 则默认状态为 Success。\n存活探针不会等待就绪探针成功。 如果想在执行存活探针前等待，可以定义 initialDelaySeconds，或者使用启动探针。\n4.1.2 何时该使用存活态探针?\r如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃，则不一定需要存活态探针； kubelet 将根据 Pod 的 restartPolicy 自动执行修复操作。\n如果希望容器在探测失败时被杀死并重新启动，那么请指定一个存活态探针， 并指定 restartPolicy 为 \u0026ldquo;Always\u0026rdquo; 或 \u0026ldquo;OnFailure\u0026quot;。\n4.2 就绪探针readinessProbe\r4.2.1 什么是就绪探针?\r就绪探针决定何时容器准备好开始接受流量。 这种探针在等待应用执行耗时的初始任务时非常有用，例如建立网络连接、加载文件和预热缓存。\n如果就绪探针返回的状态为失败，Kubernetes 会将该 Pod 从所有对应服务的端点中移除。\n就绪探针在容器的整个生命期内持续运行。\n4.2.2 何时该使用就绪态探针?\r如果要仅在探测成功时才开始向 Pod 发送请求流量，请指定就绪态探针。 在这种情况下，就绪态探针可能与存活态探针相同，但是规约中的就绪态探针的存在意味着 Pod 将在启动阶段不接收任何数据，并且只有在探针探测成功后才开始接收数据。\n如果希望容器能够自行进入维护状态，也可以指定一个就绪态探针， 检查某个特定于就绪态的因此不同于存活态探测的端点。\n如果应用程序对后端服务有严格的依赖性，可以同时实现存活态和就绪态探针。 当应用程序本身是健康的，存活态探针检测通过后，就绪态探针会额外检查每个所需的后端服务是否可用。 这可以帮助避免将流量导向只能返回错误信息的 Pod。\n如果容器需要在启动期间加载大型数据、配置文件或执行迁移， 可以使用启动探针。 然而，如果想区分已经失败的应用和仍在处理其启动数据的应用，可能更倾向于使用就绪探针。\n4.3 启动探针startupProbe\r4.3.1 什么是启动探针?\r启动探针检查容器内的应用是否已启动。 启动探针可以用于对慢启动容器进行存活性检测，避免它们在启动运行之前就被 kubelet 杀掉。\n如果配置了这类探针，它会禁用存活检测和就绪检测，直到启动探针成功为止。\n这类探针仅在启动时执行，不像就绪探针那样周期性地运行。\n4.3.2 何时该使用启动探针？\r对于所包含的容器需要较长时间才能启动就绪的 Pod 而言，启动探针是有用的。 不再需要配置一个较长的存活态探测时间间隔，只需要设置另一个独立的配置选定， 对启动期间的容器执行探测，从而允许使用远远超出存活态时间间隔所允许的时长。\n如果容器启动时间通常超出 initialDelaySeconds + failureThreshold × periodSeconds 总值，应该设置一个启动探测，对存活态探针所使用的同一端点执行检查。 periodSeconds 的默认值是 10 秒。应该将其 failureThreshold 设置得足够高， 以便容器有充足的时间完成启动，并且避免更改存活态探针所使用的默认值。 这一设置有助于减少死锁状况的发生。\n4.4 探针的执行\r探针 操作对象 操作动作 操作结果 运行时间 存活探针 容器本身 重启容器 容器重新启动 容器整个生命周期 就绪探针 容器对应的端点 删除端点 从所有对应服务的端点中移除，不接收流量 容器整个生命周期 启动探针 容器本身 重启容器 容器重新启动 容器 Running 后执行一次 4.5 探针的使用\r使用探针来检查容器有四种不同的方法。 每个探针都必须准确定义为这四种机制中的一种：\n探针类型 描述 exec 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。 tcpSocket 对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开，则诊断被认为是成功的。 如果远程系统（容器）在打开连接后立即将其关闭，这算作是健康的。 httpGet 对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400，则诊断被认为是成功的。 grpc 使用 gRPC 执行一个远程过程调用。 目标应该实现 gRPC 健康检查。 如果响应的状态是 \u0026ldquo;SERVING\u0026rdquo;，则认为诊断成功。 探针参数 描述 initialDelaySeconds 容器启动后要等待多少秒后才启动启动、存活和就绪探针。 如果定义了启动探针，则存活探针和就绪探针的延迟将在启动探针已成功之后才开始计算。 如果 periodSeconds 的值大于 initialDelaySeconds，则 initialDelaySeconds 将被忽略。默认是 0 秒，最小值是 0。 periodSeconds 执行探测的时间间隔（单位是秒）。默认是 10 秒。最小值是 1。 timeoutSeconds 探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。 successThreshold 探针在失败后，被视为成功的最小连续成功数。默认值是 1。 存活和启动探针的这个值必须是 1。最小值是 1。 failureThreshold 探针连续失败了 failureThreshold 次之后， Kubernetes 认为总体上检查已失败：容器状态未就绪、不健康、不活跃。 默认值为 3，最小值为 1。 对于启动探针或存活探针而言，如果至少有 failureThreshold 个探针已失败， Kubernetes 会将容器视为不健康并为这个特定的容器触发重启操作。 kubelet 遵循该容器的 terminationGracePeriodSeconds 设置。 对于失败的就绪探针，kubelet 继续运行检查失败的容器，并继续运行更多探针； 因为检查失败，kubelet 将 Pod 的 Ready 状况设置为 false。 terminationGracePeriodSeconds 为 kubelet 配置从为失败的容器触发终止操作到强制容器运行时停止该容器之前等待的宽限时长。 默认值是继承 Pod 级别的 terminationGracePeriodSeconds 值（如果不设置则为 30 秒），最小值为 1。Kubernetes v1.25 以上版本新增，只对启动探针和存活探针有效。 4.6 示例\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 apiVersion: v1 kind: Namespace metadata: name: k8s-test --- apiVersion: v1 kind: ConfigMap metadata: name: probe-demo namespace: k8s-test data: default.conf: | server { listen 80; server_name localhost; keepalive_timeout 0; location / { root /usr/share/nginx/html; index index.html index.htm; } } --- apiVersion: v1 kind: Service metadata: name: probe-demo namespace: k8s-test spec: selector: app: probe-demo ports: - name: http protocol: TCP port: 80 targetPort: 80 nodePort: 31080 type: NodePort --- apiVersion: v1 kind: Pod metadata: name: probe-demo namespace: k8s-test labels: app: probe-demo spec: restartPolicy: OnFailure # Pod 的重启策略：OnFailure 表示只有在容器退出码非 0 时才会重启；Always 表示只要容器退出就重启；Never 表示从不重启 containers: - name: probe-demo image: 192.168.142.99:7891/devops/nginx:latest # command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;sleep 10\u0026#34;] # 模拟 pod 状态为 Succeeded # command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;sleep infinity\u0026#34;] # 启动探针 command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;set -ex \u0026amp;\u0026amp; nohup nginx \u0026amp;\u0026amp; touch /tmp/healthy \u0026amp;\u0026amp; sleep 30 \u0026amp;\u0026amp; rm -f /tmp/healthy \u0026amp;\u0026amp; sleep 600\u0026#34;] # startupProbe: # exec: # command: # - cat # - /tmp/healthy # initialDelaySeconds: 5 # 容器启动后要等待 5 秒后才启动探针 # periodSeconds: 5 # 每隔 5 秒执行一次探针 # timeoutSeconds: 1 # 探测的超时时间为 1 秒 # successThreshold: 1 # 探针在失败后，被视为成功的最小连续成功数 # failureThreshold: 3 # 探针连续失败了 3 次之后， Kubernetes 认为总体上检查已失败 # livenessProbe: # exec: # command: # - cat # - /tmp/healthy # initialDelaySeconds: 5 # periodSeconds: 5 # timeoutSeconds: 1 # failureThreshold: 3 readinessProbe: exec: command: - cat - /tmp/healthy initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 1 failureThreshold: 3 volumeMounts: - name: config-volume mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf volumes: - name: config-volume configMap: name: probe-demo items: - key: default.conf path: default.conf 4.7 实操展示\r4.8 流程分析\r","date":"2023-02-18T00:00:00Z","permalink":"/p/k8s_lifecycle_and_probes/","title":"Kubernetes 生命周期与探针详解"},{"content":"\r为了解决 OpenVPN 与 代理软件冲突的问题\nwfg/docker-openvpn-client: OpenVPN client with killswitch and proxy servers; built on Alpine (github.com)\n构建文件\rDockerfile\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 FROM alpine:3.17 RUN apk add --no-cache \\ bash \\ bind-tools \\ iptables \\ ip6tables \\ openvpn COPY . /usr/local/bin ENV KILL_SWITCH=on ENTRYPOINT [ \u0026#34;entry.sh\u0026#34; ] killswitch\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail iptables --insert OUTPUT \\ ! --out-interface tun0 \\ --match addrtype ! --dst-type LOCAL \\ ! --destination \u0026#34;$(ip -4 -oneline addr show dev eth0 | awk \u0026#39;NR == 1 { print $4 }\u0026#39;)\u0026#34; \\ --jump REJECT # 创建静态路由，允许访问指定的子网 # 例如：ALLOWED_SUBNETS 在 docker-compose.yaml 中配置 default_gateway=$(ip -4 route | awk \u0026#39;$1 == \u0026#34;default\u0026#34; { print $3 }\u0026#39;) for subnet in ${1//,/ }; do ip route add \u0026#34;$subnet\u0026#34; via \u0026#34;$default_gateway\u0026#34; iptables --insert OUTPUT --destination \u0026#34;$subnet\u0026#34; --jump ACCEPT done # 为 OpenVPN 服务器地址打洞 # $config 是 OpenVPN 设置的： # “第一个 --config 文件的名称。在程序初始化时设置并在 SIGHUP 时重置。” global_port=$(awk \u0026#39;$1 == \u0026#34;port\u0026#34; { print $2 }\u0026#39; \u0026#34;${config:?\u0026#34;config file not found by kill switch\u0026#34;}\u0026#34;) global_protocol=$(awk \u0026#39;$1 == \u0026#34;proto\u0026#34; { print $2 }\u0026#39; \u0026#34;${config:?\u0026#34;config file not found by kill switch\u0026#34;}\u0026#34;) remotes=$(awk \u0026#39;$1 == \u0026#34;remote\u0026#34; { print $2, $3, $4 }\u0026#39; \u0026#34;${config:?\u0026#34;config file not found by kill switch\u0026#34;}\u0026#34;) ip_regex=\u0026#39;^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$\u0026#39; while IFS= read -r line; do # 读取去除注释的行 IFS=\u0026#34; \u0026#34; read -ra remote \u0026lt;\u0026lt;\u0026lt; \u0026#34;${line%%\\#*}\u0026#34; address=${remote[0]} port=${remote[1]:-${global_port:-1194}} protocol=${remote[2]:-${global_protocol:-udp}} if [[ $address =~ $ip_regex ]]; then iptables --insert OUTPUT --destination \u0026#34;$address\u0026#34; --protocol \u0026#34;$protocol\u0026#34; --destination-port \u0026#34;$port\u0026#34; --jump ACCEPT else for ip in $(dig -4 +short \u0026#34;$address\u0026#34;); do iptables --insert OUTPUT --destination \u0026#34;$ip\u0026#34; --protocol \u0026#34;$protocol\u0026#34; --destination-port \u0026#34;$port\u0026#34; --jump ACCEPT echo \u0026#34;$ip $address\u0026#34; \u0026gt;\u0026gt; /etc/hosts done fi done \u0026lt;\u0026lt;\u0026lt; \u0026#34;$remotes\u0026#34; entry.sh\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail cleanup() { kill TERM \u0026#34;$openvpn_pid\u0026#34; exit 0 } is_enabled() { [[ ${1,,} =~ ^(true|t|yes|y|1|on|enable|enabled)$ ]] } # Either a specific file name or a pattern. if [[ $CONFIG_FILE ]]; then config_file=$(find /config -name \u0026#34;$CONFIG_FILE\u0026#34; 2\u0026gt; /dev/null | sort | shuf -n 1) else config_file=$(find /config -name \u0026#39;*.conf\u0026#39; -o -name \u0026#39;*.ovpn\u0026#39; 2\u0026gt; /dev/null | sort | shuf -n 1) fi if [[ -z $config_file ]]; then echo \u0026#34;no openvpn configuration file found\u0026#34; \u0026gt;\u0026amp;2 exit 1 fi echo \u0026#34;using openvpn configuration file: $config_file\u0026#34; openvpn_args=( \u0026#34;--config\u0026#34; \u0026#34;$config_file\u0026#34; \u0026#34;--cd\u0026#34; \u0026#34;/config\u0026#34; ) if is_enabled \u0026#34;$KILL_SWITCH\u0026#34;; then openvpn_args+=(\u0026#34;--route-up\u0026#34; \u0026#34;/usr/local/bin/killswitch.sh $ALLOWED_SUBNETS\u0026#34;) fi # Docker secret that contains the credentials for accessing the VPN. if [[ $AUTH_SECRET ]]; then openvpn_args+=(\u0026#34;--auth-user-pass\u0026#34; \u0026#34;/run/secrets/$AUTH_SECRET\u0026#34;) fi openvpn \u0026#34;${openvpn_args[@]}\u0026#34; \u0026amp; openvpn_pid=$! trap cleanup TERM wait $openvpn_pid 构建和启动\rdocker-compose.yaml\r公网镜像：ghcr.io/wfg/openvpn-client\n构建镜像：\n1 docker compose build 启动 1 docker compose up -d 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 services: openvpn-client: image: openvpn-client:latest # ghcr.io/wfg/openvpn-client build: context: ./ dockerfile: Dockerfile container_name: openvpn-client cap_add: - NET_ADMIN environment: # - HTTP_PROXY=on - SOCKS_PROXY=on devices: - /dev/net/tun:/dev/net/tun volumes: - ./local:/config - ./local:/data/vpn ports: # - 8080:8080 # HTTP_PROXY port - 1080:1080 # SOCKS_PROXY port restart: unless-stopped 结合代理软件使用\r使用 Clash 软件中的 规则配置 分流策略\n1 2 3 4 5 6 7 proxies: - { name: OpenVPN, server: 127.0.0.1, port: 1080, type: socks5 } rules: - IP-CIDR,192.168.142.0/24,OpenVPN - IP-CIDR,10.10.10.0/24,OpenVPN - IP-CIDR,172.16.0.0/24,OpenVPN 其他\r除了 OpenVPN，还可以将 EasyConnect 等校园网 VPN （访问知网免费）经常使用的代理软件客户端部署到 Docker 中，并暴露 HTTP 或者 Socks ，并配置分流规则，达到优雅的访问其他网络环境的效果。\n1 2 - { name: \u0026#39;CNKI\u0026#39;, type: socks5, server: 10.10.10.45, port: 57080} - \u0026#39;DOMAIN-SUFFIX,cnki.net,CNKI\u0026#39; ","date":"2022-08-03T00:00:00Z","permalink":"/p/docker_openvpn_client_proxy_integration/","title":"基于Docker的OpenVPN客户端与代理集成解决方案"},{"content":"环境准备\r关闭防火墙和邮件系统\r1 2 3 4 5 6 7 8 9 # 查看防火墙状态 firewall-cmd --state # 临时停止防火墙 systemctl stop firewalld.service # 禁止防火墙开机启动 systemctl disable firewalld.service systemctl stop postfix.service systemctl disable postfix.service 关闭selinux\r1 2 3 4 5 6 # 查看selinux状态 getenforce # 临时关闭selinux setenforce 0 # 永久关闭selinux sed -i \u0026#39;s/^ *SELINUX=enforcing/SELINUX=disabled/g\u0026#39; /etc/selinux/config 关闭swap\r1 2 3 4 5 6 # 临时关闭swap swapoff -a # 永久关闭swap sed -i.bak \u0026#39;/swap/s/^/#/\u0026#39; /etc/fstab # 查看 free -g 调整内核参数及模块\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 cat \u0026lt;\u0026lt; EOF \u0026gt; /etc/sysctl.d/kubernetes.conf ############################################################################################# # 调整虚拟内存 ################################################################################# # Default: 30 # 0 - 任何情况下都不使用swap。 # 1 - 除非内存不足（OOM），否则不使用swap。 vm.swappiness = 0 # 内存分配策略 #0 - 表示内核将检查是否有足够的可用内存供应用进程使用；如果有足够的可用内存，内存申请允许；否则，内存申请失败，并把错误返回给应用进程。 #1 - 表示内核允许分配所有的物理内存，而不管当前的内存状态如何。 #2 - 表示内核允许分配超过所有物理内存和交换空间总和的内存 vm.overcommit_memory=1 # OOM时处理 # 1关闭，等于0时，表示当内存耗尽时，内核会触发OOM killer杀掉最耗内存的进程。 vm.panic_on_oom=0 # vm.dirty_background_ratio 用于调整内核如何处理必须刷新到磁盘的脏页。 # Default value is 10. # 该值是系统内存总量的百分比，在许多情况下将此值设置为5是合适的。 # 此设置不应设置为零。 vm.dirty_background_ratio = 5 # 内核强制同步操作将其刷新到磁盘之前允许的脏页总数 # 也可以通过更改 vm.dirty_ratio 的值（将其增加到默认值30以上（也占系统内存的百分比））来增加 # 推荐 vm.dirty_ratio 的值在60到80之间。 vm.dirty_ratio = 60 # vm.max_map_count 计算当前的内存映射文件数。 # mmap 限制（vm.max_map_count）的最小值是打开文件的ulimit数量（cat /proc/sys/fs/file-max）。 # 每128KB系统内存 map_count应该大约为1。 因此，在32GB系统上，max_map_count为262144。 # Default: 65530 vm.max_map_count = 2097152 ############################################################################################# # 调整文件 ############################################################################################# fs.may_detach_mounts = 1 # 增加文件句柄和inode缓存的大小，并限制核心转储。 fs.file-max = 2097152 fs.nr_open = 2097152 fs.suid_dumpable = 0 # 文件监控 fs.inotify.max_user_instances=8192 fs.inotify.max_user_watches=524288 fs.inotify.max_queued_events=16384 ############################################################################################# # 调整网络设置 ############################################################################################# # 为每个套接字的发送和接收缓冲区分配的默认内存量。 net.core.wmem_default = 25165824 net.core.rmem_default = 25165824 # 为每个套接字的发送和接收缓冲区分配的最大内存量。 net.core.wmem_max = 25165824 net.core.rmem_max = 25165824 # 除了套接字设置外，发送和接收缓冲区的大小 # 必须使用net.ipv4.tcp_wmem和net.ipv4.tcp_rmem参数分别设置TCP套接字。 # 使用三个以空格分隔的整数设置这些整数，分别指定最小，默认和最大大小。 # 最大大小不能大于使用net.core.wmem_max和net.core.rmem_max为所有套接字指定的值。 # 合理的设置是最小4KiB，默认64KiB和最大2MiB缓冲区。 net.ipv4.tcp_wmem = 20480 12582912 25165824 net.ipv4.tcp_rmem = 20480 12582912 25165824 # 增加最大可分配的总缓冲区空间 # 以页为单位（4096字节）进行度量 net.ipv4.tcp_mem = 65536 25165824 262144 net.ipv4.udp_mem = 65536 25165824 262144 # 为每个套接字的发送和接收缓冲区分配的最小内存量。 net.ipv4.udp_wmem_min = 16384 net.ipv4.udp_rmem_min = 16384 # 启用TCP窗口缩放，客户端可以更有效地传输数据，并允许在代理方缓冲该数据。 net.ipv4.tcp_window_scaling = 1 # 提高同时接受连接数。 net.ipv4.tcp_max_syn_backlog = 10240 # 将net.core.netdev_max_backlog的值增加到大于默认值1000 # 可以帮助突发网络流量，特别是在使用数千兆位网络连接速度时， # 通过允许更多的数据包排队等待内核处理它们。 net.core.netdev_max_backlog = 65536 # 增加选项内存缓冲区的最大数量 net.core.optmem_max = 25165824 # 被动TCP连接的SYNACK次数。 net.ipv4.tcp_synack_retries = 2 # 允许的本地端口范围。 net.ipv4.ip_local_port_range = 2048 65535 # 防止TCP时间等待 # Default: net.ipv4.tcp_rfc1337 = 0 net.ipv4.tcp_rfc1337 = 1 # 减少tcp_fin_timeout连接的时间默认值 net.ipv4.tcp_fin_timeout = 15 # 积压套接字的最大数量。 # Default is 128. net.core.somaxconn = 32768 # 打开syncookies以进行SYN洪水攻击保护。 net.ipv4.tcp_syncookies = 1 # 避免Smurf攻击 # 发送伪装的ICMP数据包，目的地址设为某个网络的广播地址，源地址设为要攻击的目的主机， # 使所有收到此ICMP数据包的主机都将对目的主机发出一个回应，使被攻击主机在某一段时间内收到成千上万的数据包 net.ipv4.icmp_echo_ignore_broadcasts = 1 # 为icmp错误消息打开保护 net.ipv4.icmp_ignore_bogus_error_responses = 1 # 启用自动缩放窗口。 # 如果延迟证明合理，这将允许TCP缓冲区超过其通常的最大值64K。 net.ipv4.tcp_window_scaling = 1 # 打开并记录欺骗，源路由和重定向数据包 net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.default.log_martians = 1 # 告诉内核有多少个未附加的TCP套接字维护用户文件句柄。 万一超过这个数字， # 孤立的连接会立即重置，并显示警告。 # Default: net.ipv4.tcp_max_orphans = 65536 net.ipv4.tcp_max_orphans = 65536 # 不要在关闭连接时缓存指标 net.ipv4.tcp_no_metrics_save = 1 # 启用RFC1323中定义的时间戳记： # Default: net.ipv4.tcp_timestamps = 1 net.ipv4.tcp_timestamps = 1 # 启用选择确认。 # Default: net.ipv4.tcp_sack = 1 net.ipv4.tcp_sack = 1 # 增加 tcp-time-wait 存储桶池大小，以防止简单的DOS攻击。 # net.ipv4.tcp_tw_recycle 已从Linux 4.12中删除。请改用net.ipv4.tcp_tw_reuse。 net.ipv4.tcp_max_tw_buckets = 14400 net.ipv4.tcp_tw_reuse = 1 # accept_source_route 选项使网络接口接受设置了严格源路由（SSR）或松散源路由（LSR）选项的数据包。 # 以下设置将丢弃设置了SSR或LSR选项的数据包。 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 # 打开反向路径过滤 net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 # ICMP重定向接受 net.ipv4.conf.all.accept_redirects = 1 net.ipv4.conf.default.accept_redirects = 1 net.ipv4.conf.all.secure_redirects = 1 net.ipv4.conf.default.secure_redirects = 1 # 发送所有IPv4 ICMP重定向数据包。 net.ipv4.conf.all.send_redirects = 1 net.ipv4.conf.default.send_redirects = 1 # 开启IP转发. net.ipv4.ip_forward = 1 # 禁止IPv6 net.ipv6.conf.lo.disable_ipv6=1 net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 # 要求iptables不对bridge的数据进行处理 net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-arptables = 1 # arp缓存 # 存在于 ARP 高速缓存中的最少层数，如果少于这个数，垃圾收集器将不会运行。缺省值是 128 net.ipv4.neigh.default.gc_thresh1=2048 # 保存在 ARP 高速缓存中的最多的记录软限制。垃圾收集器在开始收集前，允许记录数超过这个数字 5 秒。缺省值是 512 net.ipv4.neigh.default.gc_thresh2=4096 # 保存在 ARP 高速缓存中的最多记录的硬限制，一旦高速缓存中的数目高于此，垃圾收集器将马上运行。缺省值是 1024 net.ipv4.neigh.default.gc_thresh3=8192 # 持久连接 net.ipv4.tcp_keepalive_time = 600 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 10 # conntrack表 net.nf_conntrack_max=1048576 net.netfilter.nf_conntrack_max=1048576 net.netfilter.nf_conntrack_buckets=262144 net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30 net.netfilter.nf_conntrack_tcp_timeout_time_wait=30 net.netfilter.nf_conntrack_tcp_timeout_close_wait=15 net.netfilter.nf_conntrack_tcp_timeout_established=300 ############################################################################################# # 调整内核参数 ############################################################################################# # 地址空间布局随机化（ASLR）是一种用于操作系统的内存保护过程，可防止缓冲区溢出攻击。 # 这有助于确保与系统上正在运行的进程相关联的内存地址不可预测， # 因此，与这些流程相关的缺陷或漏洞将更加难以利用。 # Accepted values: 0 = 关闭, 1 = 保守随机化, 2 = 完全随机化 kernel.randomize_va_space = 2 # 调高 PID 数量 kernel.pid_max = 65536 kernel.threads-max=30938 # coredump kernel.core_pattern=core # 决定了检测到soft lockup时是否自动panic，缺省值是0 kernel.softlockup_all_cpu_backtrace=1 kernel.softlockup_panic=1 EOF 1 2 3 4 5 lsmod |grep conntrack modprobe ip_conntrack modprobe br_netfilter sysctl --system sysctl -p /etc/sysctl.d/kubernetes.conf 开启ipvs\r不开启ipvs将会使用iptables进行数据包转发，但是效率低，所以推荐开通ipvs，使用\n1 2 3 4 5 6 7 8 cat \u0026lt;\u0026lt;EOF\u0026gt; /etc/sysconfig/modules/ipvs.modules #!/bin/bash modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack EOF 1 2 3 4 5 6 7 8 # 加载模块 chmod 755 /etc/sysconfig/modules/ipvs.modules \u0026amp;\u0026amp; bash /etc/sysconfig/modules/ipvs.modules \u0026amp;\u0026amp; lsmod | grep -e ip_vs -e nf_conntrack_ipv4 # 安装了ipset软件包 yum install ipset -y # 安装管理工具ipvsadm yum install ipvsadm -y 同步服务器时间\r1 yum install chrony -y 1 2 3 4 5 6 7 8 9 # 主节点配置 vim /etc/chrony.conf server ntp.aliyun.com iburst allow 192.168.142.0/24 # 从节点 vim /etc/chrony.conf server controller iburst 1 2 systemctl restart chronyd.service systemctl enable chronyd.service 修改 hostname 和 hosts（可选）\r1 2 3 4 5 6 7 8 9 10 11 12 hostnamectl set-hostname master hostnamectl set-hostname node1 hostnamectl set-hostname node2 cat \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt; EOF 192.168.231.3 master 192.168.231.4 node1 EOF echo -e \u0026#34;\\033[32m [主机解析] ==\u0026gt; OK \\033[0m\u0026#34; 系统日志优化\r1 2 mkdir /var/log/journal mkdir /etc/systemd/journald.conf.d 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 cat \u0026gt; /etc/systemd/journald.conf.d/99-prophet.conf \u0026lt;\u0026lt; EOF [journal] # 持久化保存到磁盘 Storage=persistent # 压缩历史日志 Compress=yes # 设置同步日志到磁盘的间隔时间为 5 分钟。 SyncIntervalSec=5m # 用于控制日志消息的速率限制，以防止日志过度增长。 RateLimitInterval=30s RateLimitBurst=1000 # 最大占用空间 10G SystemMaxUse=10G # 控制日志数据的大小 RuntimeMaxUse=500M # 但日志文件最大 200M SystemMaxFileSize=200M # 日志保持时间2周 MaxRetentionSec=2week # 不将日志转发到syslog ForwardToSyslog=no # 设置在达到日志文件大小限制时 # 是否启用“密封”模式，即不允许写入新的日志数据，直到文件被清理 SystemMaxSealing=yes RuntimeMaxSealing=yes # 启用运行时的看门狗定时器，以在一段时间内没有新日志写入时重新启动 systemd-journald。 RuntimeWatchdogSec=1h ## 设置是否启用自动整理和优化日志文件，以减少磁盘碎片和提高性能 SystemMaxFilesTidy=yes RuntimeMaxFilesTidy=yes # 设置运行时日志文件的最大寿命，即日志文件的保留时间。 RuntimeMaxFileSec=7days # 自动选择最合适的日志存储模式（volatile、persistent、auto、none） StorageAuto=yes EOF 1 2 systemctl restart systemd-journald echo -e \u0026#34;\\033[32m [日志优化] ==\u0026gt; OK \\033[0m\u0026#34; Docker 安装\r1 2 3 4 5 6 7 8 9 set -e sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl start docker sudo systemctl enable docker vim /etc/docker/daemon.json 1 2 3 4 5 6 { \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=systemd\u0026#34;], \u0026#34;insecure-registries\u0026#34;: [ \u0026#34;192.168.142.99\u0026#34; ] } 1 2 sudo systemctl restart docker docker login 192.168.142.99 安装 k8s\r安装 kubelet、kubeadm、kubectl\r添加kubernetes源\n1 2 3 4 5 6 7 8 9 10 cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=0 repo_gpgcheck=0 gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF 然后安装 kubeadm、kubelet、kubectl\n1 2 3 4 5 6 7 # 查看版本，最新版 1.23.5-0 yum list kubeadm --showduplicates | sort -r yum install -y kubelet-1.23.5-0 kubectl-1.23.5-0 kubeadm-1.23.5-0 kubeadm version # kubeadm version: \u0026amp;version.Info{Major:\u0026#34;1\u0026#34;, Minor:\u0026#34;23\u0026#34;, GitVersion:\u0026#34;v1.23.5\u0026#34;, GitCommit:\u0026#34;c285e781331a3785a7f436042c65c5641ce8a9e9\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, BuildDate:\u0026#34;2022-03-16T15:57:37Z\u0026#34;, GoVersion:\u0026#34;go1.17.8\u0026#34;, Compiler:\u0026#34;gc\u0026#34;, Platform:\u0026#34;linux/amd64\u0026#34;} 修改kubelet配置\n1 2 # 修改配置文件 /etc/sysconfig/kubelet (默认不存在该文件，需要新增创建） KUBELET_EXTRA_ARGS=--root-dir=/var/lib/kubelet 启动kubelet服务，并设置开机自启\n1 2 systemctl start kubelet systemctl enable kubelet 初始化k8s集群\r通过配置文件初始化\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 cat \u0026lt;\u0026lt;EOF\u0026gt; kubeadm.yaml apiVersion: kubeadm.k8s.io/v1beta3 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.4.27 # apiserver 节点内网IP bindPort: 6443 nodeRegistration: criSocket: /run/containerd/containerd.sock # 修改为containerd imagePullPolicy: IfNotPresent name: master taints: - effect: NoSchedule key: node-role.kubernetes.io/master --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: type: CoreDNS # dns类型 type: CoreDNS etcd: local: dataDir: /var/lib/etcd imageRepository: registry.aliyuncs.com/google_containers # 修改这个镜像能下载 kind: ClusterConfiguration kubernetesVersion: 1.23.5 # k8s版本 networking: dnsDomain: cluster.local podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 scheduler: {} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs # kube-proxy 模式 EOF 1 kubeadm init --config kubeadm.yaml 📌\r重要\rkubeadm安装的证书默认是一年\nkube-proxy 模式是 iptables，可以通过kubectl edit configmap kube-proxy -n kube-system修改\n命令式初始化\r1 2 3 4 5 6 7 8 9 10 11 kubeadm init \\ --control-plane-endpoint k8svip:8443 \\ --kubernetes-version=v1.23.5 \\ --service-cidr=172.96.0.0/12 \\ --pod-network-cidr=172.244.0.0/16 \\ --image-repository registry.aliyuncs.com/google_containers \\ --upload-certs kubeadm join k8svip:8443 --token i8zsn5.dakiqfxexdxn7wdt \\ --discovery-token-ca-cert-hash sha256:61ed5a0941ecf47078dac91c4389bc8abb9c761149e869a40f9c3da859b39dba \\ --control-plane --certificate-key fc133d520c12052c9391e075c3aa6dda456599b70b1335aba2c3e0680e75af6e 1 2 3 4 5 6 7 kubeadm init --kubernetes-version v1.23.5 \\ --apiserver-advertise-address 172.16.0.185 \\ --image-repository registry.aliyuncs.com/google_containers \\ --service-cidr 172.96.0.0/12 \\ --pod-network-cidr 172.244.0.0/16 \\ --upload-certs \\ --v=5 安装Calico网络插件（master节点执行）\r1 2 3 # 下载 curl https://docs.projectcalico.org/manifests/calico.yaml -o calico.yaml kubectl apply -f calico.yaml node 初始化\r最后输出的token，是节点加入的信息，一般是两个小时内有效\n1 2 kubeadm join 192.168.99.34:6443 --token gp024k.zemwtue9qnn9ghps \\ --discovery-token-ca-cert-hash sha256:43457cba87e58a5d14c1181643091d1dfc256e23a4fb7b5a006643fea1ea9471 在master 上执行kubeadm token list 查看 ，在node上运行\n1 2 3 4 5 kubeadm token create kubeadm token list # TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS # upobd4.jocvrwd5jpl7x8n6 23h 2020-04-19T23:26:06+08:00 authentication,signing The default bootstrap token generated by \u0026#39;kubeadm init\u0026#39;. system:bootstrappers:kubeadm:default-node-token 首先查到master上的token upobd4.jocvrwd5jpl7x8n6\n1 kubeadm join master:6443 --token upobd4.jocvrwd5jpl7x8n6 --discovery-token-unsafe-skip-ca-verification 新节点加入\n1 kubeadm token create --print-join-command 安装自动补全工具\r1 2 3 4 yum install -y bash-completion source /usr/share/bash-completion/bash_completion source \u0026lt;(kubectl completion bash) echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 部署验证集群\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 cat \u0026lt;\u0026lt; EOF \u0026gt; nginx-ds.yaml apiVersion: apps/v1 kind: Deployment metadata : name: deploy-game namespace: default spec: replicas: 8 selector: matchLabels: app: game release: stabel template: metadata: labels: app: game release: stabel env: test spec: imagePullSecrets: - name: kkregcred containers: - name: game image: registry.cn-beijing.aliyuncs.com/kaikai136/docker-2048:v1 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: game-svc namespace: default spec: type: NodePort selector: app: game release: stabel ports: - name: http port: 80 targetPort: 80 nodePort: 32000 protocol: TCP EOF 1 2 3 4 5 kubectl apply -f nginx-ds.yaml kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port=80 --type=NodePort kubectl get pod,svc 测试 calico 网络\r1 kubectl run busybox --image docker.io/library/busybox:1.28 --image-pull-policy=IfNotPresent --restart=Never --rm -it busybox -- sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 / # ping www.baidu.com PING www.baidu.com (180.101.50.242): 56 data bytes 64 bytes from 180.101.50.242: seq=0 ttl=51 time=7.880 ms 64 bytes from 180.101.50.242: seq=1 ttl=51 time=7.247 ms ^C #可以看到能访问网络，说明calico网络插件已经被正常安装了 --- www.baidu.com ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 7.247/7.563/7.880 ms / # nslookup kubernetes.default.svc.cluster.local Server: 172.96.0.10 Address 1: 172.96.0.10 kube-dns.kube-system.svc.cluster.local Name: kubernetes.default.svc.cluster.local Address 1: 172.96.0.1 kubernetes.default.svc.cluster.local #172.96.0.10 就是我们coreDNS的clusterIP，说明coreDNS配置好了。 #解析内部Service的名称，是通过coreDNS去解析的。 #注意： #busybox要用指定的1.28版本，不能用最新版本，最新版本，nslookup会解析不到dns和ip 安装其他工具\r自动补全工具\r1 2 3 4 yum install -y bash-completion source /usr/share/bash-completion/bash_completion source \u0026lt;(kubectl completion bash) echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc ","date":"2022-05-31T00:00:00Z","image":"/p/centos7_kubernetes_1.23.5_installation_guide/cover_chinese.png","permalink":"/p/centos7_kubernetes_1.23.5_installation_guide/","title":"CentOS 7 上安装 Kubernetes 1.23.5 集群完整指南"},{"content":"\r📝\r备注\rdeviantony/docker-elk: The Elastic stack (ELK) powered by Docker and Compose. (github.com)\n1 2 3 git clone https://github.com/deviantony/docker-elk cd docker-elk vim .env 1 2 docker compose up setup docker compose up 1 2 docker compose down -v setup docker compose down -v ","date":"2022-05-22T00:00:00Z","permalink":"/p/docker_elk_installation_guide/","title":"Docker 部署 ELK 指南"},{"content":"centos7内核升级\r下载内核源\n1 rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm 装最新版本内核\n1 yum --enablerepo=elrepo-kernel install -y kernel-lt 查看\n1 cat /boot/grub2/grub.cfg |grep menuentry 设置开机从新内核启动\n1 grub2-set-default \u0026#34;CentOS Linux (4.4.221-1.el7.elrepo.x86_64) 7 (Core)\u0026#34; 关闭防火墙\r1 2 systemctl stop firewalld systemctl disable firewalld 安装常用工具\r1 2 3 4 5 6 yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget vim git net-tools dos2unix lsof tcpdump lrzsz telnet bash-completion.noarch conntrack-tools linux 补全 yum install libvirt-bash-completion bash-completion gedit-plugin-bracketcompletion gedit-plugin-wordcompletion libguestfs-bash-completion -y setenforce\r1 2 setenforce 0 sed -i \u0026#39;/^SELINUX=/ s/enforcing/disabled/\u0026#39; /etc/selinux/config 更新histroy和shell保持时间\r1 2 3 4 5 vim /etc/profile export HISTSIZE=100 export TMOUT=300 关掉swap分区\r1 2 3 4 5 swapoff -a # 如果想永久关掉swap分区，打开如下文件注释掉swap哪一行即可. sed -i \u0026#39;/ swap / s/^\\(.*\\)$/#\\1/g\u0026#39; /etc/fstab echo \u0026#34;vm.swappiness = 0\u0026#34;\u0026gt;\u0026gt; /etc/sysctl.conf sysctl -p 邮件服务关闭\r1 2 systemctl stop postfix.service systemctl disable postfix.service 日志优化\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 mkdir /var/log/journal mkdir /etc/systemd/journald.conf.d cat \u0026gt; /etc/systemd/journald.conf.d/99-prophet.conf \u0026lt;\u0026lt; EOF [journal] # 持久化保存到磁盘 Storage=persistent # 压缩历史日志 Compress=yes SyncIntervalSec=5m RateLimitInterval=30s RateLimitBurst=1000 # 最大占用空间 10G SystemMaxUse=10G # 但日志文件最大 200M SystemMaxFileSize=200M # 日志保持时间2周 MaxRetentionSec=2week # 不将日志转发到syslog ForwardToSyslog=no EOF systemctl restart systemd-journald 加载ipvs\r1 2 3 4 5 6 7 8 9 10 cat \u0026gt; /etc/sysconfig/modules/ipvs.modules \u0026lt;\u0026lt;EOF modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 EOF chmod 755 /etc/sysconfig/modules/ipvs.modules bash /etc/sysconfig/modules/ipvs.modules 文件优化\recho \u0026lsquo;* - nofile 65535 \u0026rsquo; \u0026raquo;/etc/security/limits.conf echo \u0026lsquo;vm.max_map_count=262144 \u0026rsquo; \u0026raquo;/etc/security/limits.conf\nsysctl vm.overcommit_memory=1\ntail -1 /etc/security/limits.conf sysctl -p\n内核优化\r1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cat \u0026gt;\u0026gt;/etc/sysctl.conf\u0026lt;\u0026lt;EOF net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 30 net.ipv4.tcp_keepalive_time = 1200 net.ipv4.ip_local_port_range = 4000 65000 net.ipv4.tcp_max_syn_backlog = 262144 net.ipv4.tcp_max_tw_buckets = 36000 net.ipv4.route.gc_timeout = 100 net.ipv4.tcp_syn_retries = 1 net.ipv4.tcp_synack_retries = 1 net.core.somaxconn = 262144 net.core.netdev_max_backlog = 262144 net.ipv4.tcp_max_orphans = 16384 net.ipv4.tcp_mem = 94500000 915000000 927000000 EOF sysctl -p ??? note \u0026ldquo;详细\u0026rdquo; net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时，启用cookies来处理，可防范少量SYN攻击，默认为0，表示关闭；\nnet.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接，默认为0，表示关闭；\rnet.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收，默认为0，表示关闭。\rnet.ipv4.tcp_fin_timeout = 30 表示如果套接字由本端要求关闭，这个参数决定了它保持在FIN-WAIT-2状态的时间。\rnet.ipv4.tcp_keepalive_time = 1200 表示当keepalive起用的时候，TCP发送keepalive消息的频度。缺省是2小时，改为20分钟。\rnet.ipv4.ip_local_port_range = 1024 65000 表示用于向外连接的端口范围。缺省情况下很小：32768到61000，改为1024到65000。\rnet.ipv4.tcp_max_syn_backlog = 8192 表示SYN队列的长度，默认为1024，加大队列长度为8192，可以容纳更多等待连接的网络连接数。\rnet.ipv4.tcp_max_tw_buckets = 5000表示系统同时保持TIME_WAIT套接字的最大数量，如果超过这个数字，TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000，改为5000。对于Apache、Nginx等服务器，上几行的参数可以很好地减少TIME_WAIT套接字数量，但是对于Squid，效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量，避免Squid服务器被大量的TIME_WAIT套接字拖死。\rsshd 服务优化\r1 2 3 4 5 6 7 vim /etc/ssh/sshd_config GSSAPIAuthentication 赋值为no UseDNS,赋值为 no(该项默认不启用的，要把前面的#删除掉) 重启systemctl restart sshd 二、系统相关命令\r1.CPU 核数 和 型号 和 主频\r1 cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c 2.测试磁盘 IO 性能\r1).hdparm 命令\rhdparm 命令提供了一个命令行的接口用于读取和设置IDE或SCSI硬盘参数，注意该命令只能测试磁盘的读取速率。\n1 2 3 4 5 [root@server-68.2.stage.polex.io var ]$ hdparm -Tt /dev/polex_pv/varvol /dev/polex_pv/varvol: Timing cached reads: MB in 2.00 seconds = 7803.05 MB/sec Timing buffered disk reads: MB in 3.01 seconds = 374.90 MB/sec 2).dd 命令\rLinux dd 命令用于读取、转换并输出数据。dd 可从标准输入或文件中读取数据，根据指定的格式来转换数据，再输出到文件、设备或标准输出。\n我们可以利用 dd 命令的复制功能，测试某个磁盘的 IO 性能，须要注意的是 dd 命令只能大致测出磁盘的 IO 性能，不是非常准确。\n1 2 3 4 5 6 7 8 [root@server-68.2.stage.polex.io var ]$ time dd if=/dev/zero of=test.file bs=1G count= oflag=direct + records in + records out bytes (2.1 GB) copied, 13.5487 s, MB/s real 0m13.556s user 0m0.000s sys 0m0.888s ??? note \u0026ldquo;参数说明\u0026rdquo; 可以看到，该分区磁盘写入速率为 159M/s，其中：\n/dev/zero 伪设备，会产生空字符流，对它不会产生 IO 。\rif 参数用来指定 dd 命令读取的文件。\rof 参数用来指定 dd 命令写入的文件。\rbs 参数代表每次写入的块的大小。\rcount 参数用来指定写入的块的个数。\rofflag=direc 参数测试 IO 时必须指定，代表直接写如磁盘，不使用 cache 。\r3).FIO测试磁盘IO性能\rfio 命令是专门测试 iops 的命令，比 dd 命令准确，fio 命令的参数很多，这里举几个例子供大家参考：\n1 yum install fio 1 2 3 4 5 6 7 8 9 10 # 随机读： fio -filename=/dev/sda1 -direct=1 -iodepth 1 -thread -rw=randread -ioengine=psync -bs=4k -size=60G -numjobs=64 -runtime=10 -group_reporting -name=file # 顺序读： fio -filename=/dev/sda1 -direct=1 -iodepth 1 -thread -rw=read -ioengine=psync -bs=4k -size=60G -numjobs=64 -runtime=10 -group_reporting -name=file # 随机写： fio -filename=/dev/sda1 -direct=1 -iodepth 1 -thread -rw=randwrite -ioengine=psync -bs=4k -size=60G -numjobs=64 -runtime=10 -group_reporting -name=file # 顺序读： fio -filename=/dev/sda1 -direct=1 -iodepth 1 -thread -rw=write -ioengine=psync -bs=4k -size=60G -numjobs=64 -runtime=10 -group_reporting -name=file # 混合随机读写： fio -filename=/dev/sda1 -direct=1 -iodepth 1 -thread -rw=randrw -rwmixread=30 -ioengine=psync -bs=4k -size=60G -numjobs=64 -runtime=10 -group_reporting -name=file -ioscheduler=noop 文中bw=1532.2KB/s, iops=383即是测试出的iops\n??? note \u0026ldquo;参数说明\u0026rdquo;\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 filename=/dev/sda1：测试文件名称，通常选择需要测试的盘的data目录 direct=1：测试过程绕过机器自带的buffer。使测试结果更真实 rw=randwrite：测试随机写的I/O rw=randrw：测试随机写和读的I/O rw=randread：测试随机读的I/O bs=4k：单次io的块文件大小为4k bsrange=512-2048：同上，提定数据块的大小范围 size=60g：本次的测试文件大小为60g，以每次4k的io进行测试 numjobs=64：本次的测试线程为64 runtime=10：测试时间为10秒，如果不写则一直将5g文件分4k每次写完为止 ioengine=psync：io引擎使用pync方式 rwmixwrite=30：在混合读写的模式下，写占30% group_reporting：关于显示结果的，汇总每个进程的信息。 此外 lockmem=1g：只使用1g内存进行测试 zero_buffers：用0初始化系统buffer nrfiles=8：每个进程生成文件的数量。 4).iostat 命令\r先用iostat查看磁盘io 是否读写负载很高 如果%util接近100%,表明I/O请求太多,I/O系统已经满负荷，磁盘可能存在瓶颈,一般%util大于70%,I/O压力就比较大，读取速度有较多的wait，然后再看其他的参数\n1 2 3 yum install sysstat iostat -x 1 10 ??? note \u0026ldquo;解释\u0026rdquo;\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 rrqm/s:每秒进行merge的读操作数目。即delta(rmerge)/s wrqm/s:每秒进行merge的写操作数目。即delta(wmerge)/s r/s:每秒完成的读I/O设备次数。即delta(rio)/s w/s:每秒完成的写I/0设备次数。即delta(wio)/s rsec/s:每秒读扇区数。即delta(rsect)/s wsec/s:每秒写扇区数。即delta(wsect)/s rKB/s:每秒读K字节数。是rsec/s的一半，因为每扇区大小为512字节 wKB/s:每秒写K字节数。是wsec/s的一半 avgrq-sz:平均每次设备I/O操作的数据大小(扇区)。即delta(rsect+wsect)/delta(rio+wio) avgqu-sz:平均I/O队列长度。即delta(aveq)/s/1000(因为aveq的单位为毫秒) await:平均每次设备I/O操作的等待时间(毫秒)。即delta(ruse+wuse)/delta(rio+wio) svctm:平均每次设备I/O操作的服务时间(毫秒)。即delta(use)/delta(rio+wio) %util:一秒中有百分之多少的时间用于I/O操作,或者说一秒中有多少时间I/O队列是非空的 5).iotop 命令\r找出使用io高的进程的工具 直接执行 iotop 命令\n1 yum install iotop -y 3.sar 命令\rsar -u 1 1 表示查看CPU利用率 每隔1秒写入1次 sar 命令是分析系统瓶颈的神器，可以用来查看 CPU 、内存、磁盘、网络等性能。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [root@server-68.2.stage.polex.io var ]$ sar -d -p Linux 3.10.-693.5..el7.x86_64 (server-) // _x86_64_ ( CPU) :: PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util :: PM sda 1.00 0.00 3.00 3.00 0.01 9.00 9.00 0.90 :: PM sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 :: PM polex_pv-rootvol 1.00 0.00 3.00 3.00 0.01 9.00 9.00 0.90 :: PM polex_pv-varvol 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 :: PM polex_pv-homevol 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 :: PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util :: PM sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 :: PM sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 :: PM polex_pv-rootvol 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 :: PM polex_pv-varvol 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 :: PM polex_pv-homevol 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 Average: DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util Average: sda 0.50 0.00 1.50 3.00 0.00 9.00 9.00 0.45 Average: sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 Average: polex_pv-rootvol 0.50 0.00 1.50 3.00 0.00 9.00 9.00 0.45 Average: polex_pv-varvol 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 Average: polex_pv-homevol 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 其中， “-d”参数代表查看磁盘性能，“-p”参数代表将 dev 设备按照 sda，sdb……名称显示，“1”代表每隔1s采取一次数值，“2”代表总共采取2次数值。\n??? note \u0026ldquo;参数说明\u0026rdquo; await：平均每次设备 I/O 操作的等待时间（以毫秒为单位）。\nsvctm：平均每次设备 I/O 操作的服务时间（以毫秒为单位）。\r%util：一秒中有百分之几的时间用于 I/O 操作。\r对于磁盘 IO 性能，一般有如下评判标准：\r正常情况下 svctm 应该是小于 await 值的，而 svctm 的大小和磁盘性能有关，CPU 、内存的负荷也会对 svctm 值造成影响，过多的请求也会间接的导致 svctm 值的增加。\rawait 值的大小一般取决与 svctm 的值和 I/O 队列长度以 及I/O 请求模式，如果 svctm 的值与 await 很接近，表示几乎没有 I/O 等待，磁盘性能很好，如果 await 的值远高于 svctm 的值，则表示 I/O 队列等待太长，系统上运行的应用程序将变慢，此时可以通过更换更快的硬盘来解决问题。\r%util 项的值也是衡量磁盘 I/O 的一个重要指标，如果 %util 接近 100% ，表示磁盘产生的 I/O 请求太多，I/O 系统已经满负荷的在工作，该磁盘可能存在瓶颈。长期下去，势必影响系统的性能，可以通过优化程序或者通过更换更高、更快的磁盘来解决此问题。\r4.vmstat 命令\r1 2 3 [root@server-68.2.stage.polex.io var ]$ vmstat procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 输出结果中，bi bo 可以表示磁盘当前性能：\nbi 块设备每秒接收的块数量，这里的块设备是指系统上所有的磁盘和其他块设备，默认块大小是 1024 byte 。\nbo 块设备每秒发送的块数量，例如我们读取文件，bo 就要大于0。bi 和 bo 一般都要接近 0，不然就是 IO 过于频繁，需要调整。\n5.uptime命令\r1 2 3 4 5 6 uptime 输出的信息依次为：系统现在的时间、系统从上次开机到现在运行了多长时间、有多少用户登陆、系统在1分钟内， 5分钟内，15分钟内的平均负载。 load average的三个值如果长期大于系统中CPU个数，说明CPU负载很高，可能会影响系统性能。 6.TCP/IP 相关\r1).netstat命令\r1 netstat -an |grep tcp 查看当前所有的TCP连接相关信息 2).Socket Statistics命令\r之前采用netstat命令会发现在服务器繁忙的时候效果不理想，有时会占用高达90%以上的CPU。\n而Socket Statistics（ss）命令底层使用TCP协议栈中用于分析统计的tcp_diag模块，所以速度更快、更高效。\nss -t 显示系统目前所有TCP连接的连接情况\n??? note \u0026ldquo;介绍\u0026rdquo; -t 只显示TCP连接信息\n-a 显示所有连接信息\r-u 只显示UDP连接信息\r几乎所有的Linux系统都会默认包含netstat命令，但并非都会包含ss命令(Centos默认包含)。\rss命令包含iproute工具集中，这是一套可以支持IPv4/IPv6网络的用于管理TCP/UDP/IP网络的工具集。\r如果找不到ss命令，可以如下安装此工具集： yum install iproute iproute-doc\r7.磁盘I/O、吞吐量和存储IOPS\r磁盘I/O、吞吐量和IOPS性能指标\r云服务器磁盘存储性能指标包括磁盘I/O、IOPS和吞吐量，服务器百科来详细说下磁盘I/O、存储IOPS、和吞吐量意义详解，及磁盘IO、IOPS和吞吐量之间的关系：\n存储IOPS：磁盘IOPS是指一秒内磁盘进行多少次I/O读写； 磁盘I/O：I/O，即input/output，磁盘的输入输出，输入指的是对磁盘写入数据，输出指的是从磁盘读出数据，磁盘I/O可以理解为读写。应用发起的一次或多次数据请求，I/O请求的数据量又称I/O大小，单位为KiB，例如4KiB、256KiB、1024KiB等； 磁盘吞吐量：每秒磁盘I/O的流量，即磁盘写入加上读出的数据的大小。 存储IOPS、磁盘I/O和吞吐量关系公式\r存储IOPS、磁盘I/O和吞吐量之间的关系公式为：吞吐量 = IOPS * I/O大小\n也就是说，磁盘I/O越大，IOPS越高，那么磁盘那么每秒I/O的吞吐量就越高，根据公式服务器百科网认为IOPS和吞吐量的数值越高越好，实际上磁盘IOPS和吞吐量两个参数是有最大值的。\n关于云服务器磁盘存储I/O性能详细说明可以参考阿里云ecs6.com关于云服务器存储I/O性能的说明。\n常见的linux监控命令\rfree\ndf\ntop / htop\nuptime\niftop\niostat\niotop\nvmstat\nnetstat\nnethogs 每个进程用了多少流量\n","date":"2021-06-23T00:00:00Z","permalink":"/p/centos7_system_optimization_guide/","title":"CentOS 7系统优化与部署手册"},{"content":"介绍\r官方仓库地址：https://github.com/neurobin/shc\n一个通用的 shell 脚本编译器。Shc 接受一个在命令行中指定的脚本，并生成 C 源代码。然后生成的源代码被编译并链接以生成一个剥离的二进制可执行文件\n编译后的二进制文件仍然依赖于 shell 代码第一行中指定的 shell (即 shebang)(即 # ！/bin/sh) ，因此 shc 不会创建完全独立的二进制文件。\nShc 本身不是 cc 这样的编译器，而是对 shell 脚本进行编码和加密，并生成具有附加过期功能的 C 源代码。然后，它使用系统编译器编译一个精简的二进制文件，其行为与原始脚本完全一样。在执行时，已编译的二进制文件将使用 shell-c 选项解密并执行代码\n安装\r1 2 3 yum install epel-release yum -y install gcc gcc-c++ libstdc++-devel yum -y install shc 1 2 3 4 [root@template mnt]# shc -v shc parse(-f): No source file specified shc Usage: shc [-e date] [-m addr] [-i iopt] [-x cmnd] [-l lopt] [-o outfile] [-rvDSUHCABh] -f script 测试\r1 2 shc -v -rf HelloWorld.sh ./HelloWorld.x ","date":"2020-10-12T00:00:00Z","permalink":"/p/shc_shell_script_compiler/","title":"使用SHC将Shell脚本编译为可执行文件"},{"content":"安装 k8s\r安装 kubelet、kubeadm、kubectl\r添加 k8s 下载源 1 2 3 4 5 6 7 8 sudo apt-get install -y ca-certificates curl software-properties-common apt-transport-https curl curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add - sudo tee /etc/apt/sources.list.d/kubernetes.list \u0026lt;\u0026lt;EOF deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main EOF sudo apt-get update 安装 kubeadm、kubelet、kubectl 1 apt install -y kubelet=1.23.5-00 kubectl=1.23.5-00 kubeadm=1.23.5-00 启动kubelet服务，并设置开机自启 1 2 systemctl start kubelet systemctl enable kubelet 初始化k8s集群\r1 2 3 4 5 6 7 kubeadm init --kubernetes-version v1.23.5 \\ --apiserver-advertise-address 192.168.142.63 \\ --image-repository registry.aliyuncs.com/google_containers \\ --service-cidr 172.95.0.0/12 \\ --pod-network-cidr 172.245.0.0/16 \\ --upload-certs \\ --v=5 安装网络插件\rcalico-ipv4.yaml\n","date":"2020-05-31T00:00:00Z","permalink":"/p/k8s_installation_ubuntu_18_04_1_23_5/","title":"Ubuntu 18.04系统安装Kubernetes 1.23.5集群完整指南"}]