标签归档:Kubernetes

Hermes Agent — 在 K3s / K8s 中运行指南

本文基于官方 Docker 文档,将 Hermes Agent 迁移到 Kubernetes / K3s 环境,使用 StatefulSet 管理持久化工作负载。

1. 前置准备

  • K3s 或 K8s 集群已就绪(本文以 K3s 为例)
  • 节点上已有 containerd(K3s 默认内置)
  • 推荐安装 nerdctl 作为容器管理工具(参考:在 K3s 节点上安装并使用 nerdctl
  • 镜像:nousresearch/hermes-agent:latest

2. 初始化配置(持久化数据目录)

在首次运行前,需要先执行一次 Setup Wizard,将 API Keys 等配置写入宿主机目录,再挂载进容器使用。

这里建议使用 nerdctl 运行,其他的方法需自行探索。

# 在目标节点上创建数据目录
mkdir -p /var/lib/hermes-data

# 使用 nerdctl 运行一次性 setup 容器(交互模式)
sudo nerdctl run -it --rm \
  -v /var/lib/hermes-data:/opt/data \
  nousresearch/hermes-agent:latest setup

配置完成后的数据目录结构

/var/lib/hermes-data/
├── .env            # API Keys 与密钥
├── config.yaml     # 主配置文件
├── SOUL.md         # Agent 人格 / 身份设定
├── sessions/       # 会话历史
├── memories/       # 持久记忆
├── skills/         # 已安装的技能
├── cron/           # 定时任务定义
├── hooks/          # 事件钩子
├── logs/           # 运行日志
└── skins/          # 自定义 CLI 皮肤

3. 部署 Gateway 后台服务(StatefulSet)

这里直接给出参考 yaml,按需调整:

---
apiVersion: v1
kind: Service
metadata:
  name: gateway
  namespace: hermes
spec:
  selector:
    app: gateway
  ports:
    - name: api
      port: 8642
      targetPort: 8642
  type: ClusterIP
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: data
  namespace: hermes
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 50Gi
  storageClassName: nfs-hhus3
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: gateway
  namespace: hermes
spec:
  serviceName: gateway
  replicas: 1
  selector:
    matchLabels:
      app: gateway
  template:
    metadata:
      labels:
        app: gateway
    spec:
      nodeSelector:
        hosthatch/zone: lax
      containers:
        - name: gateway
          image: nousresearch/hermes-agent:latest
          args: ["gateway", "run"]
          ports:
            - containerPort: 8642
          env:
            - name: TZ
              value: "Asia/Shanghai"
          volumeMounts:
            - name: hermes-data
              mountPath: /opt/data
          resources:
            requests:
              memory: "1Gi"
              cpu: "500m"
            limits:
              memory: "4Gi"
              cpu: "2"
      volumes:
        - name: hermes-data
          persistentVolumeClaim:
            claimName: data

4. 部署 Dashboard 仪表盘(StatefulSet)

直接给出参考 yaml,按需调整:

apiVersion: v1
kind: Service
metadata:
  name: dashboard
  namespace: hermes
spec:
  selector:
    app: dashboard
  ports:
    - name: web
      port: 9119
      targetPort: 9119
  type: ClusterIP   # 按需改为 NodePort / LoadBalancer
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: dashboard
  namespace: hermes
spec:
  serviceName: dashboard
  replicas: 1
  selector:
    matchLabels:
      app: dashboard
  template:
    metadata:
      labels:
        app: dashboard
    spec:
      nodeSelector:
        hosthatch/zone: lax
      containers:
        - name: dashboard
          image: nousresearch/hermes-agent:latest
          args: ["dashboard"]
          #args: ["dashboard", "--host", "0.0.0.0", "--insecure"]
          ports:
            - containerPort: 9119
          env:
            # 指向 Gateway Service 的 ClusterIP DNS 名称
            - name: GATEWAY_HEALTH_URL
              value: "http://gateway.hermes.svc.cluster.local:8642"
            - name: GATEWAY_HEALTH_TIMEOUT
              value: "3"
          volumeMounts:
            - name: hermes-data
              mountPath: /opt/data
              readOnly: true    # Dashboard 只读数据目录
          resources:
            requests:
              memory: "256Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"

      volumes:
        - name: hermes-data
          persistentVolumeClaim:
            claimName: data

可以使用 port-forward 安全的访问仪表盘,不建议对外暴露:

kubectl port-forward -n hermes svc/hermes-dashboard 9119:9119
# 浏览器访问 http://localhost:9119

5. 运行交互式 CLI 聊天

在已部署并配置好的数据目录基础上,可随时进行交互式聊天,使用 kubectl exec 进入 Gateway 容器

kubectl exec -it -n hermes gateway-0 -- /opt/hermes/.venv/bin/hermes

Refs

在 K3s 节点上安装并使用 nerdctl

适用场景:K3s 默认不附带 nerdctl,但其内置的 containerd 与 nerdctl 完全兼容。本教程讲解如何在 K3s 节点上以最小代价安装 nerdctl,并正确指向 K3s 的 containerd socket,无需重复安装 containerd 或 CNI。

一、背景与原理

工具 说明
ctr containerd 内置调试工具,与 Docker CLI 不兼容,功能有限
crictl CRI 调试工具,K3s 自带,面向 Kubernetes 运维
nerdctl Docker 兼容 CLI,支持 run/build/compose推荐日常使用

K3s 的 containerd socket 路径为 /run/k3s/containerd/containerd.sock,而非标准路径 /run/containerd/containerd.sock。只需在配置中指向该路径,nerdctl 即可接管 K3s 容器管理。

K3s 已自带 CNI 插件(flannel/calico 等),查看 K3s 节点已有的 Pod 和镜像无需额外 CNI。若需要 nerdctl run 启动独立容器并连接网络,则需要补充安装 CNI 插件(见第四节)。

二、安装 nerdctl(仅二进制)

K3s 节点已有 containerd,只需下载 nerdctl 的精简包(不含 containerd/CNI,体积小)。

2.1 下载二进制

# 查询最新版本(或手动前往 https://github.com/containerd/nerdctl/releases 查看)
NERDCTL_VERSION=$(curl -s https://api.github.com/repos/containerd/nerdctl/releases/latest \
  | grep tag_name | cut -d '"' -f4 | tr -d 'v')

echo "最新版本: ${NERDCTL_VERSION}"

# 下载精简包(仅 nerdctl 二进制)
curl -LO "https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz"

ARM64 节点(如树莓派、ARM 服务器)将 amd64 替换为 arm64

curl -LO "https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-arm64.tar.gz"

2.2 解压并安装

# 解压到 /usr/local/bin
sudo tar Cxzvf /usr/local/bin nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz nerdctl

# 验证安装
nerdctl --version

三、配置 nerdctl 指向 K3s containerd

nerdctl 默认连接 /run/containerd/containerd.sock,在 K3s 节点上需要修改为 K3s 专用路径。

3.1 创建配置文件

sudo mkdir -p /etc/nerdctl

sudo tee /etc/nerdctl/nerdctl.toml > /dev/null <<EOF
# nerdctl 全局配置,适配 K3s 节点
address        = "/run/k3s/containerd/containerd.sock"
namespace      = "k8s.io"
EOF

说明

  • address:K3s containerd 的 socket 路径
  • namespace:K3s 所有容器和镜像均存储在 k8s.io 命名空间下

3.2 验证连接

# 列出 K3s 命名空间下的所有容器(等同于 kubectl get pods 的容器视角)
sudo nerdctl ps -a

# 列出镜像
sudo nerdctl images

如果能看到 K3s 系统 Pod(如 coredns、traefik 等),说明配置成功。

四、安装 CNI 插件(按需,用于 nerdctl run)

如果只需要查看 K3s 已有容器和镜像,可跳过此节。

只有当你需要用 nerdctl run 启动独立容器(即非 Kubernetes 管理的容器)时,才需要 CNI 插件。K3s 自带的 CNI 仅供 Kubernetes 使用,nerdctl 的独立容器网络需要单独配置。

4.1 下载官方 CNI 插件

CNI_VERSION=$(curl -s https://api.github.com/repos/containernetworking/plugins/releases/latest \
  | grep tag_name | cut -d '"' -f4)

curl -LO "https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/cni-plugins-linux-amd64-${CNI_VERSION}.tgz"

# 安装到标准路径
sudo mkdir -p /opt/cni/bin
sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-${CNI_VERSION}.tgz

4.2 创建默认网络配置

sudo mkdir -p /etc/cni/net.d

sudo tee /etc/cni/net.d/10-nerdctl-bridge.conflist > /dev/null <<EOF
{
  "cniVersion": "1.0.0",
  "name": "nerdctl-bridge",
  "plugins": [
    {
      "type": "bridge",
      "bridge": "nerdctl0",
      "isGateway": true,
      "ipMasq": true,
      "ipam": {
        "type": "host-local",
        "ranges": [
          [{"subnet": "10.88.0.0/16"}]
        ],
        "routes": [{"dst": "0.0.0.0/0"}]
      }
    },
    {
      "type": "portmap",
      "capabilities": {"portMappings": true}
    },
    {
      "type": "firewall"
    }
  ]
}
EOF

注意:此桥接网络(10.88.0.0/16)仅供 nerdctl 管理的独立容器使用,不会影响 K3s 自身网络。

4.3 验证独立容器运行

# 注意:启动独立容器时需使用默认命名空间(不加 --namespace k8s.io)
# 或在 nerdctl.toml 中临时切换,推荐直接在命令行覆盖:
sudo nerdctl --namespace default run -d --name test-nginx -p 8080:80 nginx:alpine

# 确认运行
sudo nerdctl --namespace default ps
curl http://localhost:8080

五、常用命令速查

所有命令均需 sudo(或将当前用户加入 containerd 相关权限组)。

查看 K3s 容器和镜像

# 列出所有容器(K3s 管理)
sudo nerdctl ps -a

# 列出镜像
sudo nerdctl images

# 查看容器日志
sudo nerdctl logs <容器ID或名称>

# 进入容器终端
sudo nerdctl exec -it <容器ID或名称> sh

镜像管理

# 拉取镜像(拉取后可直接被 K3s Pod 使用)
sudo nerdctl pull nginx:alpine

# 查看镜像详情
sudo nerdctl inspect <镜像ID>

# 删除镜像
sudo nerdctl rmi <镜像ID>

# 从 tar 包导入镜像(常用于离线环境)
sudo nerdctl load < image.tar

# 导出镜像为 tar 包
sudo nerdctl save nginx:alpine -o nginx.tar

构建镜像(需安装 BuildKit,见第六节)

sudo nerdctl build -t myapp:v1 /path/to/dockerfile-dir

配合 kubectl 使用本地镜像

# 构建并打 tag 到 k8s.io 命名空间
sudo nerdctl --namespace k8s.io build -t myapp:local .

# 然后在 Pod spec 中指定 imagePullPolicy: Never 即可使用本地镜像
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
  - name: myapp
    image: myapp:local
    imagePullPolicy: Never
EOF

六、可选:安装 BuildKit(支持 nerdctl build)

nerdctl 构建镜像需要 BuildKit daemon。

BUILDKIT_VERSION=$(curl -s https://api.github.com/repos/moby/buildkit/releases/latest \
  | grep tag_name | cut -d '"' -f4)

curl -LO "https://github.com/moby/buildkit/releases/download/${BUILDKIT_VERSION}/buildkit-${BUILDKIT_VERSION}.linux-amd64.tar.gz"

sudo tar Cxzvf /usr/local buildkit-${BUILDKIT_VERSION}.linux-amd64.tar.gz

# 创建 systemd 服务
sudo tee /etc/systemd/system/buildkit.service > /dev/null <<EOF
[Unit]
Description=BuildKit
After=network.target containerd.service

[Service]
ExecStart=/usr/local/bin/buildkitd \
  --addr unix:///run/buildkit/buildkitd.sock \
  --containerd-worker-addr /run/k3s/containerd/containerd.sock
Restart=always

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now buildkit

Refs

Kubernetes kubectl –raw 使用指南

什么是 kubectl –raw?

kubectl --raw 是一个强大的底层工具,允许你直接访问 Kubernetes API Server 的 REST API,绕过 kubectl 的客户端逻辑、准入控制器(Admission Controllers)和 Webhook。

为什么需要 –raw?

标准 kubectl 的请求流程

kubectl 命令
    ↓
客户端验证和处理
    ↓
Admission Controllers
    ↓
Mutating Webhooks (修改请求)
    ↓
Validating Webhooks (验证请求)
    ↓
API Server 存储到 etcd

kubectl –raw 的请求流程

kubectl --raw
    ↓
直接 HTTP 请求到 API Server
    ↓
绕过大部分中间件
    ↓
直接操作 etcd

适用场景

  1. 绕过 Webhook 干扰 – 当 Mutating/Validating Webhook 阻止正常操作时
  2. 调试 API Server – 排查 kubectl 客户端与 API Server 的交互问题
  3. 访问特殊端点 – 访问 metrics、healthz 等非资源端点
  4. 绕过客户端限制 – kubectl 版本不支持某些新特性时
  5. 性能测试 – 直接测试 API Server 响应时间
  6. 修复僵尸资源 – 清理被控制器锁定的资源状态

基本语法

# 基本格式
kubectl get --raw <API-PATH>

# 或在某些版本中
kubectl --raw <API-PATH>

常用操作示例

1. GET 请求 – 查询资源

查看集群级别资源

# 获取所有节点
kubectl get --raw /api/v1/nodes | jq .

# 获取特定节点
kubectl get --raw /api/v1/nodes/node-name | jq .

# 获取节点状态
kubectl get --raw /api/v1/nodes/node-name/status | jq .

# 获取所有命名空间
kubectl get --raw /api/v1/namespaces | jq .

查看命名空间级别资源

# 获取 default 命名空间的所有 Pod
kubectl get --raw /api/v1/namespaces/default/pods | jq .

# 获取特定 Pod
kubectl get --raw /api/v1/namespaces/default/pods/pod-name | jq .

# 获取 Deployment
kubectl get --raw /apis/apps/v1/namespaces/default/deployments/deploy-name | jq .

# 获取 Service
kubectl get --raw /api/v1/namespaces/default/services/svc-name | jq .

查看子资源

# Pod 日志
kubectl get --raw /api/v1/namespaces/default/pods/pod-name/log

# Pod 状态
kubectl get --raw /api/v1/namespaces/default/pods/pod-name/status | jq .

# Service 的 Endpoint
kubectl get --raw /api/v1/namespaces/default/endpoints/service-name | jq .

2. PUT 请求 – 完整更新资源

# 更新节点(先获取,修改,再替换)
kubectl get --raw /api/v1/nodes/node-name > node.json

# 编辑 node.json 文件
vim node.json

# 替换(注意:不同版本语法可能不同)
kubectl replace --raw /api/v1/nodes/node-name -f node.json

# 或使用 kubectl proxy 方式
kubectl proxy --port=8001 &
curl -X PUT \
  -H "Content-Type: application/json" \
  -d @node.json \
  http://localhost:8001/api/v1/nodes/node-name

实战案例:清除节点僵尸条件

# 获取节点当前状态
kubectl get --raw /api/v1/nodes/node-name > /tmp/node.json

# 使用 jq 删除特定条件
jq 'del(.status.conditions[] | select(.type == "EtcdIsVoter"))' \
  /tmp/node.json > /tmp/node-fixed.json

# 更新节点状态
kubectl replace --raw /api/v1/nodes/node-name/status -f /tmp/node-fixed.json

3. POST 请求 – 创建资源

# 创建 Pod
cat > pod.json <<EOF
{
  "apiVersion": "v1",
  "kind": "Pod",
  "metadata": {
    "name": "test-pod",
    "namespace": "default"
  },
  "spec": {
    "containers": [{
      "name": "nginx",
      "image": "nginx:latest"
    }]
  }
}
EOF

kubectl create --raw /api/v1/namespaces/default/pods -f pod.json

4. DELETE 请求 – 删除资源

# 删除 Pod
kubectl delete --raw /api/v1/namespaces/default/pods/pod-name

# 使用 kubectl proxy 方式
kubectl proxy --port=8001 &
curl -X DELETE http://localhost:8001/api/v1/namespaces/default/pods/pod-name

5. PATCH 请求 – 部分更新

# JSON Patch (精确的操作指令)
kubectl patch --raw /api/v1/nodes/node-name \
  --type='json' \
  -p='[
    {"op": "add", "path": "/metadata/labels/new-label", "value": "new-value"},
    {"op": "remove", "path": "/status/conditions/0"}
  ]'

# Strategic Merge Patch (合并式更新)
kubectl patch --raw /api/v1/nodes/node-name \
  --type='merge' \
  -p '{
    "metadata": {
      "labels": {
        "environment": "production"
      }
    }
  }'

# Merge Patch (简单合并)
kubectl patch --raw /api/v1/nodes/node-name \
  --type='merge' \
  -p '{"spec":{"unschedulable":true}}'

API 路径规则

核心 API 组 (Core API Group)

# 格式
/api/v1/<resource-type>                        # 集群级别
/api/v1/namespaces/
<namespace>/<resource-type> # 命名空间级别

# 示例
/api/v1/nodes
/api/v1/nodes/node-name
/api/v1/nodes/node-name/status
/api/v1/namespaces/default/pods
/api/v1/namespaces/default/pods/pod-name
/api/v1/namespaces/default/services

命名 API 组 (Named API Groups)

# 格式
/apis/
<group>/<version>/<resource-type>
/apis/
<group>/<version>/namespaces/<ns>/<resource-type>

# 常用 API 组示例
/apis/apps/v1/deployments                           # Deployment
/apis/apps/v1/namespaces/default/deployments
/apis/batch/v1/cronjobs                             # CronJob
/apis/networking.k8s.io/v1/ingresses                # Ingress
/apis/rbac.authorization.k8s.io/v1/clusterroles     # ClusterRole
/apis/storage.k8s.io/v1/storageclasses              # StorageClass

子资源 (Subresources)

# 状态子资源
/api/v1/nodes/
<name>/status
/apis/apps/v1/namespaces/
<ns>/deployments/<name>/status

# 日志
/api/v1/namespaces/
<ns>/pods/<name>/log
/api/v1/namespaces/
<ns>/pods/<name>/log?container=container-name

# 执行命令
/api/v1/namespaces/
<ns>/pods/<name>/exec

# 端口转发
/api/v1/namespaces/
<ns>/pods/<name>/portforward

# 代理
/api/v1/nodes/
<name>/proxy
/api/v1/namespaces/
<ns>/pods/<name>/proxy
/api/v1/namespaces/
<ns>/services/<name>/proxy

特殊端点

查看 API 资源

# 列出所有 API 版本
kubectl get --raw /apis | jq '.groups[].name'

# 查看特定 API 组
kubectl get --raw /apis/apps/v1 | jq .

# 列出所有可用资源
kubectl get --raw /api/v1 | jq '.resources[].name'

# OpenAPI 规范
kubectl get --raw /openapi/v2 | jq . > openapi.json

集群信息

# 版本信息
kubectl get --raw /version | jq .

# 健康检查
kubectl get --raw /healthz
kubectl get --raw /livez
kubectl get --raw /readyz

# API Server 标志
kubectl get --raw /debug/flags/v

# Metrics
kubectl get --raw /metrics

认证和授权

# 检查当前用户权限
kubectl get --raw /apis/authorization.k8s.io/v1/selfsubjectaccessreviews \
  -X POST \
  -d '{
    "apiVersion": "authorization.k8s.io/v1",
    "kind": "SelfSubjectAccessReview",
    "spec": {
      "resourceAttributes": {
        "namespace": "default",
        "verb": "get",
        "resource": "pods"
      }
    }
  }'

使用 kubectl proxy 的方式

kubectl --raw 不可用或语法复杂时,可以使用 proxy 方式:

# 启动代理
kubectl proxy --port=8001 &

# 使用 curl 访问
curl http://localhost:8001/api/v1/nodes | jq .

# GET 请求
curl http://localhost:8001/api/v1/namespaces/default/pods

# POST 请求
curl -X POST \
  -H "Content-Type: application/json" \
  -d @pod.json \
  http://localhost:8001/api/v1/namespaces/default/pods

# PUT 请求
curl -X PUT \
  -H "Content-Type: application/json" \
  -d @node.json \
  http://localhost:8001/api/v1/nodes/node-name/status

# DELETE 请求
curl -X DELETE \
  http://localhost:8001/api/v1/namespaces/default/pods/pod-name

# 停止代理
pkill -f "kubectl proxy"

实战案例

案例 1: 绕过 Webhook 修改节点标签

# 问题:Mutating Webhook 拦截标签修改
# 解决:直接通过 API 修改

# 1. 获取节点
kubectl get --raw /api/v1/nodes/node-name > node.json

# 2. 使用 jq 添加标签
jq '.metadata.labels["custom-label"] = "custom-value"' node.json > node-updated.json

# 3. 替换节点
kubectl replace --raw /api/v1/nodes/node-name -f node-updated.json

案例 2: 清理僵尸 Finalizer

# 问题:资源因 finalizer 无法删除
# 解决:直接清空 finalizers

# 1. 获取资源
kubectl get --raw /api/v1/namespaces/stuck-namespace > ns.json

# 2. 清空 finalizers
jq '.spec.finalizers = []' ns.json > ns-clean.json

# 3. 更新
kubectl replace --raw /api/v1/namespaces/stuck-namespace/finalize -f ns-clean.json

案例 3: 批量查询资源状态

#!/bin/bash
# 批量检查节点状态

for node in $(kubectl get nodes -o name | cut -d/ -f2); do
  echo "=== Node: $node ==="
  kubectl get --raw /api/v1/nodes/$node/status | \
    jq -r '.status.conditions[] | select(.type=="Ready") | 
    "Status: \(.status), Reason: \(.reason)"'
done

案例 4: 性能测试

#!/bin/bash
# 测试 API Server 响应时间

echo "Testing API Server performance..."
for i in {1..10}; do
  time kubectl get --raw /api/v1/nodes > /dev/null 2>&1
done

案例 5: 导出所有资源

#!/bin/bash
# 导出命名空间的所有资源

NAMESPACE="default"
OUTPUT_DIR="./k8s-backup"
mkdir -p $OUTPUT_DIR

# 导出 Pods
kubectl get --raw /api/v1/namespaces/$NAMESPACE/pods | \
  jq . > $OUTPUT_DIR/pods.json

# 导出 Services
kubectl get --raw /api/v1/namespaces/$NAMESPACE/services | \
  jq . > $OUTPUT_DIR/services.json

# 导出 Deployments
kubectl get --raw /apis/apps/v1/namespaces/$NAMESPACE/deployments | \
  jq . > $OUTPUT_DIR/deployments.json

echo "Backup completed in $OUTPUT_DIR"

注意事项

1. 权限要求

# 需要相应的 RBAC 权限
# 检查权限
kubectl auth can-i get nodes
kubectl auth can-i update nodes

2. resourceVersion 冲突

# 更新时可能遇到冲突
# Error: the object has been modified; please apply your changes to the latest version

# 解决:重新获取最新版本
kubectl get --raw /api/v1/nodes/node-name > node-latest.json
# 重新修改并更新

3. 数据格式验证

# 使用 jq 验证 JSON 格式
cat resource.json | jq . > /dev/null

# 如果有错误会提示

4. 备份重要资源

# 在修改前务必备份
kubectl get --raw /api/v1/nodes/node-name > node-backup-$(date +%Y%m%d).json

5. 只读操作优先

# 先用 GET 查看,确认无误后再 PUT/PATCH
kubectl get --raw /api/v1/nodes/node-name | jq .

版本兼容性

Kubernetes 1.18+

kubectl get --raw /api/v1/nodes
kubectl create --raw /api/v1/namespaces/default/pods -f pod.json
kubectl replace --raw /api/v1/nodes/node-name -f node.json
kubectl patch --raw /api/v1/nodes/node-name --type=merge -p '{...}'
kubectl delete --raw /api/v1/namespaces/default/pods/pod-name

早期版本或不支持时

# 使用 kubectl proxy
kubectl proxy --port=8001 &
curl http://localhost:8001/api/v1/nodes

调试技巧

1. 查看完整请求

# 增加日志级别
kubectl get --raw /api/v1/nodes -v=8

2. 使用 jq 过滤输出

# 只查看节点名称
kubectl get --raw /api/v1/nodes | jq '.items[].metadata.name'

# 查看 Pod 状态
kubectl get --raw /api/v1/namespaces/default/pods | \
  jq '.items[] | {name: .metadata.name, status: .status.phase}'

3. 格式化时间戳

# 转换时间格式
kubectl get --raw /api/v1/nodes/node-name | \
  jq '.metadata.creationTimestamp | fromdate | strftime("%Y-%m-%d %H:%M:%S")'

总结

kubectl --raw 是 Kubernetes 的”瑞士军刀”,提供了:

直接访问 API – 绕过客户端限制
调试工具 – 排查 kubectl 和 API Server 问题
应急修复 – 处理 Webhook 和控制器导致的问题
性能测试 – 直接测试 API Server
学习工具 – 理解 Kubernetes API 结构

⚠️ 使用场景: 作为最后的调试和修复手段
⚠️ 不推荐: 日常操作应使用标准 kubectl 命令
⚠️ 需谨慎: 直接操作可能破坏资源状态

参考资源

kubernetes 的挂载传播(mount propagation)机制

以下内容转载自:kubernetes 的挂载传播(mount propagation)机制

概述

今天在看 kubectl-debug 这个项目的时候,看到其部署文件的 volumeMounuts 中使用了一个 mountPropagation 字段,因为不清楚这个字段的作用,就做了一下了解。mount propagation 背后的东西还是很多的,因此整理了这篇文章,顺便梳理一下知识点。

kubernetes 的 mount propagation 翻译成中文就是挂载传播。挂载传播提供了共享卷挂载的能力,它允许在同一个 Pod,甚至同一个节点内,在多个容器之间共享卷的挂载。

kubernetes 的挂载传播

卷的挂载传播由 Container.volumeMounts 的 mountPropagation 字段控制。它的值有:

  • None: 这种卷挂载将不会收到任何后续由 host 创建的在这个卷上或其子目录上的挂载。同样的,由容器创建的挂载在 host 上也是不可见的。这是默认的模式。这个其实很好理解,就是容器内和 host 的后续挂载完全隔离。
  • HostToContainer: 这种卷挂载将会收到之后所有的由 host 创建在该卷上或其子目录上的挂载。换句话说,如果 host 在卷挂载内挂载的任何内容,在容器中都是可见的。同样,如果任何具有 Bidirectional 的 Pod 挂载传播到该卷挂载上,具有 HostToContainer 的挂载传播都可以看见。整个挂载传播的流程如下:

  • Bidirectional: 这种挂载机制和 HostToContainer 类似。此外,任何在容器中创建的挂载都会传播到 host,然后传播到使用相同卷的所有 Pod 的所有容器。注意:Bidirectional 挂载传播是很危险的。可能会危害到 host 的操作系统。因此只有特权容器在允许使用它。

在了解了这几种挂载传播之后,我们可以做一些实验来验证一下,首先验证的是 None 的挂载传播类型,我们创建一个 nginx 的Pod:

apiVersion: v1
kind: Pod
metadata:
    name: mount-a
    namespace: default
    label:
      app: mount
spec:
    containers:
    - name: main
      image: nginx:latest
      volumeMounts:
      - name: testmount
        mountPath: /home
        mountPropagation: None
    volumes:
    - name: testmount
      hostPath:
        path: /mnt/

YAML

然后我们分别向 host 的 /mnt 和容器的 /home 下挂载目录并查看容器和 host 的情况:

容器中:

$ kubectl exec -it mount-a sh
$ cd /home
$ ls 
sda1

Bash

host 上:

$ cd /mnt
$ ls 
sda1

Bash

然后在 host 上创建挂载:

$ mkdir /mnt/none
$ sudo mount --bind /var /mnt/none
$ ls none
cache  empty  lib  lock  log  run  spool  tmp

Bash

这个时候,我们再看容器中的文件:

$ ls none
# 无输出

Bash

这说明 host 上在该卷下的挂载并不会改变容器中的文件。接下来我们可以在容器中按照上面的方案来验证容器中的挂载也不会影响 host 中的目录视图。这里就不展示了。接下来看一下 HostToContainer 的挂载传播,我们将上面的 Pod 的 mountPropagation 字段改成 HostToContainer,然后先取消 host 上的挂载:

sudo umoint /mnt/none

然后重新创建 Pod,和上面一样,在 host 上创建挂载,查看容器中的挂载情况:

$ ls none
cache  empty  lib  lock  log  run  spool  tmp

Bash

host 上的挂载因为 HostToContainer 机制传播到了容器中。我们继续看最后一种 Bidirectional 机制。这次我们要创建两个 Pod: mount-a, mount-b,并把 mountPropagation 字段改成 Bidirectional。注意,因为 Bidirectional 是危险的,所以只有特权容器才可以使用。因此这里还需要把容器改成特权模式,最后在 mount-a 中的容器执行挂载,验证挂载是否传播到 host 和 mount-b 的容器中。

apiVersion: v1
kind: Pod
metadata:
    name: mount-a
    namespace: default
    labels:
      app: mount
spec:
    containers:
    - name: main
      image: nginx:latest
      securityContext:
        privileged: true
      volumeMounts:
      - name: testmount
        mountPath: /home
        mountPropagation: Bidirectional
    volumes:
    - name: testmount
      hostPath:
        path: /mnt/
---
apiVersion: v1
kind: Pod
metadata:
    name: mount-b
    namespace: default
    labels:
      app: mount
spec:
    containers:
    - name: main
      image: nginx:latest
      securityContext:
        privileged: true
      volumeMounts:
      - name: testmount
        mountPath: /home
        mountPropagation: Bidirectional
    volumes:
    - name: testmount
      hostPath:
        path: /mnt/

YAML

然后进入 mount-a,创建挂载:

$ kubectl exec -it mount-a sh
$ su
$ mount --bind /var /home/none
$ ls /home/none
backups  lib    lock  mail  run    tmp
cache    local  log   opt   spool

Bash

这时候查看 host 下的 /mnt/none:

$ ls /mnt/none
backups  lib    lock  mail  run    tmp
cache    local  log   opt   spool

Bash

可以发现,容器中的挂载传播到了 host 上。这时候再查看 mount-b 中的容器。

$ ls /home/none
backups  lib    lock  mail  run    tmp
cache    local  log   opt   spool

Bash

挂载也传播到了 mount-b 的容器中。

linux mount 的几种类型

上面分析了 kubernetes 的挂载传播机制,在 linux mount 中,也有类似的概念。mount 分为下面几种:

  • shared mount: 相当于上面所说的 Bidirectional 的挂载传播
  • slave mount: 每个 slave mount 都有一个 shared master mount,挂载传播只能从 master -> slave,等同于上面的 HostToContainer, host 是 master,container 是 slave。
  • private mount: 很明显,private 就是相当于 None,挂载不会向任何一方传播。
  • unbindable mount:unbindable mount 其实就是 unbindable private mount,也就是不允许使用 --bind 的挂载。

mount namespace 的机制

kubernetes 的挂载传播不是其本身实现的,也不是 docker 之类的容器运行时提供的。这是由容器化技术的基础:linux namespace 提供的,linux namespace 当前共有 6 种:

  • cgroup namespace: 隔离 cgroup 根目录
  • pid namespace: 隔离进程 id
  • ipc namespace: 隔离 System V IPC, POSIX message queues
  • uts namespace: 隔离 Hostname 和 NIS domain name
  • user namespace: 隔离用户和用户组 ID
  • mount namespace: 隔离挂载点
  • network namespace: 隔离网络设备,网络栈,端口等

其中,mount namespace 是这篇文章的重点。我们可以通过 clone 调用来看看 mount namespace 的使用:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024*1024)
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    NULL
};

int container_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());
    mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL);
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}
int main()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    /* 启用Mount Namespace - 增加CLONE_NEWNS参数 */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

C

编译运行:

$ gcc main.c -o mount
$ sudo ./mount

Bash

然后尝试挂载,来验证挂载 MS_PRIVATE 的挂载传播问题。MS_PRIVATE 下 namespace 内和 host 应该是隔离的。MS_PRIVATE 还可以替换成 MS_UNBINDABLEMS_SLAVEMS_SHARED

关于更多的 namespace 的资料,建议看这两篇文章:

参考资料

AI提效之使用 cherry-studio + k8sgpt 实现 AI 巡检 k8s

k8sgpt 能够赋予每个人的 Kubernetes 超能力,能够用简单的语言扫描 Kubernetes 集群、诊断和分类问题。利用 k8sgptmcp 服务,可以为 LLM 赋予访问 k8s 集群的可能性。

工作原理图:

sequenceDiagram
    actor U as User
    participant CS as Cherry Studio
    participant KG as k8sgpt
    participant K as K8s API Server
    U->>+CS: Add k8sgpt MCP server
    CS->>+KG: Check k8sgpt
    KG-->>-CS: k8sgpt is work
    CS-->>-U: Success to Add MCP

    U->>+CS: Ask some Question about K8s cluster
    CS->>+KG: Get someinfo throuth MCP
    KG->>+K: Get Cluster Info By API
    K-->>-KG: Return Cluster info
    KG-->>-CS: Return Info About K8s
    CS-->>CS: Handle Info
    CS-->>-U: Return Answer about K8s cluster

安装 k8sgpt

首先安装 k8sgpt 工具:

brew install k8sgpt

配置步骤

1. 配置 k8sgpt

确保你的 kubectl 已经正确配置并能够访问目标 Kubernetes 集群:

# 验证集群连接
kubectl cluster-info

# 初始化 k8sgpt
k8sgpt auth add --backend openai --model gpt-3.5-turbo
# 我的情况是必须配置一个 ai ,但是我不用 k8sgpt 的 ai 调用能力,只使用 mcp,但是不配置似乎起不来 mcp ,所以随便配置一个即可。

2. 启动 k8sgpt MCP 服务器

k8sgpt 提供了 MCP (Model Context Protocol) 服务器功能,允许 AI 助手通过标准化协议访问 Kubernetes 集群信息:

# 启动 MCP 服务器
k8sgpt serve --mcp

3. 在 Cherry Studio 中配置 MCP

在 Cherry Studio 中添加 k8sgpt MCP 服务器:

  1. 打开 Cherry Studio 设置
  2. 导航到 MCP 服务器配置
  3. 添加新的 MCP 服务器:
    • 名称: k8sgpt
    • 类型:Stdin
    • 命令: k8sgpt
    • 参数:“`
      serve
      --mcp

实际使用场景

集群健康检查

通过 Cherry Studio,启动该 mcp 后你可以使用自然语言询问集群状态:

"请检查当前集群的整体健康状况"
"有哪些 Pod 处于异常状态?"
"最近有什么报错信息吗?"

资源分析

"分析一下集群的资源使用情况"
"哪些节点的资源使用率比较高?"
"有没有资源分配不合理的工作负载?"

故障诊断

"namespace default 下的应用为什么起不来?"
"帮我分析一下这个 deployment 的问题"
"为什么服务无法访问?"

示例 MCP Client 配置

可用于 cursor claude code 等:

{
  "mcpServers": {
    "k8sgpt": {
      "command": "k8sgpt",
      "args": [
        "serve",
        "--mcp"
      ]
    }
  }
}

总结

通过结合 Cherry Studio 和 k8sgpt,我们可以构建一个智能化的 Kubernetes 运维助手,实现:

  • 提升效率: 自然语言交互,降低操作复杂度
  • 智能诊断: AI 驱动的问题识别和解决方案推荐
  • 实时监控: 持续的集群健康状态监控
  • 知识沉淀: 问题和解决方案的智能化管理

这种 AI + DevOps 的结合方式,代表了未来运维工作的发展方向,让复杂的 Kubernetes 集群管理变得更加简单和智能。

References

Octant – 以开发人员为中心的开源 Kubernetes Web 界面

TL;DR

Octant 是一个以开发人员为中心的开源 Kubernetes Web 界面,可让您检查 Kubernetes 集群及其应用程序,能够帮助开发人员更好理解 Kubernetes 集群复杂性的平台。在这里发现的。

虽然 VMware 已结束该项目的积极开发 ,但看起来确实很好用,收藏备用。

# ArchLinux
yay -S octant-bin

# Windows
choco install octant --confirm

# MacOS
brew install octant

官网截图

Usage

octant

界面截图

References

解决 Nginx Ingress returns 413 Entity Too Large

TL;DR

配置 ingress 服务时调整一下大小即可:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cafe-ingress-with-annotations
  annotations:
    nginx.org/proxy-connect-timeout: "30s"
    nginx.org/proxy-read-timeout: "20s"
    nginx.org/client-max-body-size: "4m"
    nginx.org/server-snippets: |
      location / {
        return 302 /coffee;
      }      
spec:
  rules:
  - host: cafe.example.com
    http:
      paths:
      - path: /tea
        pathType: Prefix
        backend:
          service:
            name: tea-svc
            port:
              number: 80
      - path: /coffee
        pathType: Prefix
        backend:
          service:
            name: coffee-svc
            port:
              number: 80

References

【转】k8s 认知路线

From V

以下内容转载自 https://www.v2ex.com/t/968514#r_13557021

k8s 这个东西真的内容太多了,没有啥系统性的资料,里面各种知识点真的没法说,太多了,最好的就是看官方文档,并且结合工作当中的实践慢慢积累,才能由浅入深,只是看文档想掌握深点,个人感觉很困难。

如果你不是专做这行,只是把它当作你应用部署的底层平台的话可以给你个简单的流程做参考:
1 、云服务商买一套,或者自己搭建个 k8s 环境
2 、运行起来一套简单的前后端分离服务,这里主要练习的是多个镜像启动多个不同的 workload ,然后怎么能互相访问对接,这个环节能掌握清楚 workload service ingress 都是干嘛的,该怎么用,怎么关联
3 、你会发现当你测试环境发生重启,或者 pod 重建后,数据库数据都没了,这会你就应该研究数据持久化了,pv pvc 的概念就出来了
5 、然后你又创建了个前端服务,想修改个前端页面的配置文件参数,比如网页的 title ,其他都一模一样,但是每个服务一个镜像,太麻烦了,容器里直接修改,重建就没了,配置文件放 pv pvc ,太小题大做,这会 configmap 出现了。连接数据库的配置文件,密钥明文,太 low 了,secret 出现了
6 、前端页面镜像有 bug ,必须要重打镜像了,cicd 出现了,你是选择 docker build 还是 jenkins ,新的知识又增加了
7 、更新 workload 的镜像,问题又来了,服务会不会受损,多副本就不会受损吗?如何优雅终止,健康检查,无损更新?
8 、服务高峰期怎么应对,手动扩副本数太傻,hpa 来了

如果你想深点,做些 k8s 运维或者技术支持的,那么除了上面的必须熟悉,下面的东西必知必会
1 、清楚 k8s 的工作逻辑,master 的三大件是干嘛的,kubelet ,kube-proxy ,coredns 都是干嘛的,比如执行个创建或查询一个 workload ,系统组件之间怎么通讯的,创建一个 pod 后,容器网络和外界是怎么打通的,k8s 资源调度和分配逻辑是什么
2 、自己搭建一套 k8s 集群,多 master 的最好,master 和 worker 节点分开(一定是自己搭建,不要购买云服务商现成的容器服务,自己搭建过程你会收获不少东西)
3 、熟练使用 kubectl 命令进行各种查询分析
4 、清楚 rbac 的功能和使用

剩下的太多太多了,说不完了,
等你发现 k8s 了解差不多了,发现总得有个监控吧,prometheus 出现了
流量治理,服务分析也得有吧,istio 来了
日志得持久化保存下吧,els 来了
学了那么多了,得给老板展示下漂亮帅气的监控吧,得研究 grafana 了。
服务都上 k8s 了,集群越来越重要,万一哪天 etcd 崩了怎么办,备份总得有吧,etcd 原理和备份恢复得研究下吧。
想自建镜像仓库了? harbor 研究下。
这个云平台太贵了,业务想换个云平台,集群要迁移,velero 走起
想给 pod 限速了,想给集群加审计了。。。。

References

nginx-ingress 配置路由 302

demo ingress

这个实例中,实现将 访问 https://image.frytea.com/Avatar.jpg 请求302到 https://image.frytea.com/i/Avatar.jpg

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
  namespace: imagehost
  annotations:
    cert-manager.io/cluster-issuer: "dnspod-cluster-issuer"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      location = /Avatar.jpg {
        return 301 https://image.frytea.com/i/Avatar.jpg$is_args$args;
      }
spec:
  ingressClassName: nginx
  tls:
  - hosts:
      - image.frytea.com
      - imagehost-cdn.frytea.com
      - cdn-imagehost.frytea.com
    secretName: image-frytea-com-tls
  rules:
  - host: image.frytea.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app
            port:
              name: web

直接配置会提示报错:

➜  imagehost git:(main) ✗ kubectl apply -f ingress.yaml  
Error from server (BadRequest): error when applying patch:  
{"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"networking.k8s.io/v1\",\"kind\":\"Ingress\",\"metadata\":{\"annotati  
ons\":{\"cert-manager.io/cluster-issuer\":\"dnspod-cluster-issuer\",\"nginx.ingress.kubernetes.io/configuration-snippet\":\"location = /Avatar.jpg {\\n  return 301 ht  
tps://image.frytea.com/i/Avatar.jpg$is_args$args;\\n}\\n\"},\"name\":\"app\",\"namespace\":\"imagehost\"},\"spec\":{\"ingressClassName\":\"nginx\",\"rules\":[{\"host\  
":\"image.frytea.com\",\"http\":{\"paths\":[{\"backend\":{\"service\":{\"name\":\"app\",\"port\":{\"name\":\"web\"}}},\"path\":\"/\",\"pathType\":\"Prefix\"}]}},{\"ho  
st\":\"imagehost-cdn.frytea.com\",\"http\":{\"paths\":[{\"backend\":{\"service\":{\"name\":\"app\",\"port\":{\"name\":\"web\"}}},\"path\":\"/\",\"pathType\":\"Prefix\  
"}]}},{\"host\":\"cdn-imagehost.frytea.com\",\"http\":{\"paths\":[{\"backend\":{\"service\":{\"name\":\"app\",\"port\":{\"name\":\"web\"}}},\"path\":\"/\",\"pathType\  
":\"Prefix\"}]}}],\"tls\":[{\"hosts\":[\"image.frytea.com\",\"imagehost-cdn.frytea.com\",\"cdn-imagehost.frytea.com\"],\"secretName\":\"image-frytea-com-tls\"}]}}\n",  
"nginx.ingress.kubernetes.io/configuration-snippet":"location = /Avatar.jpg {\n  return 301 https://image.frytea.com/i/Avatar.jpg$is_args$args;\n}\n","nginx.ingress.k  
ubernetes.io/rewrite-rule":null}}}  
to:  
Resource: "networking.k8s.io/v1, Resource=ingresses", GroupVersionKind: "networking.k8s.io/v1, Kind=Ingress"  
Name: "app", Namespace: "imagehost"  
for: "ingress.yaml": error when patching "ingress.yaml": admission webhook "validate.nginx.ingress.kubernetes.io" denied the request: annotation group ConfigurationSn  
ippet contains risky annotation based on ingress configuration

这个错误来自于 Nginx Ingress Controller 自带的一个叫做 “Admission Webhook” 的安全校验机制。它的作用是在你创建或更新 Ingress 资源时进行检查,防止应用不安全或可能导致问题的配置。 很多 Nginx Ingress Controller 的默认安装配置或者管理员策略禁用限制 configuration-snippetserver-snippet 这类强大的注解

开启 Snippet 注释

使用 helm 部署的 nginx-ingress ,首先修改 Values.yaml 中的内容,启动

controller:
  allowSnippetAnnotations: true

将配置应用到集群:

helm upgrade --install ingress-nginx ingress-nginx  \
    --repo https://kubernetes.github.io/ingress-nginx  \
    --namespace ingress-nginx --create-namespace -f vaules.yaml

之后调整 ConfigMap

kubectl -n ingress-nginx edit cm ingress-nginx-controller

增加两行:

...
apiVersion: v1
data:
  allow-snippet-annotations: "true"
  annotations-risk-level: Critical
  use-forwarded-headers: "true"
kind: ConfigMap
...

其中 :

  • annotations-risk-level: Critical: 设置 Webhook 接受的最高风险门槛,确保 Snippets(作为 Critical 风险注解)在被评估时不会因为风险等级过高而被直接拒绝,从而让 allow-snippet-annotations 的设置能够生效
  • use-forwarded-headers: "true": 告诉 Nginx Ingress Controller 信任并使用由其上游代理(通常是你的云服务商提供的负载均衡器,如 AWS ELB/ALB/NLB, GCP Load Balancer, Azure Load Balancer 等)设置的 X-Forwarded-*Forwarded HTTP 头部信息,来确定原始客户端的真实信息

再重启所有 nginx controller

kubectl rollout restart -n ingress-nginx daemonset ingress-nginx-controller

之后在尝试 apply 上面的 demo 就可以成功了。

References

k8s 触发 pod 重新拉取镜像平滑升级的方法

下面介绍更新 Deployment 以重新拉取相同标签镜像的方法,不要只会杀 pod 触发了,个人最喜欢方法二

当镜像名称和标签都没有变化,但需要重新拉取镜像时(比如镜像内容已更新但标签保持不变),可以采用以下方法:

方法一:修改 Pod 模板以触发重新部署

为 Deployment 的 Pod 模板添加或更新一个注释(annotation)来触发滚动更新:

kubectl patch deployment [deployment-name] -p \
  "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"kubectl.kubernetes.io/restartedAt\":\"$(date +%s)\"}}}}}"

这会添加或更新一个时间戳注释,使 Kubernetes 认为 Pod 模板已更改,从而触发重新部署。

方法二:强制重启 Deployment

kubectl rollout restart deployment/[deployment-name]

这是 Kubernetes 1.15 及更高版本提供的便捷命令,效果与方法一类似。

方法三:修改 imagePullPolicy

确保容器的 imagePullPolicy 设置为 Always,这样每次 Pod 重启都会重新拉取镜像:

kubectl patch deployment [deployment-name] -p '{"spec":{"template":{"spec":{"containers":[{"name":"[container-name]","imagePullPolicy":"Always"}]}}}}'

设置后,可以使用方法一或方法二触发重新部署。

方法四:删除 Pod(不推荐)

手动删除 Pod,让 Deployment 控制器创建新的 Pod:

kubectl delete pod -l app=[your-app-label]

注意: 此方法不推荐用于生产环境,因为它可能导致服务中断。

最佳实践建议

  1. 始终使用唯一标签:最好的做法是为每个新版本的镜像使用唯一标签,如使用 Git commit SHA 或时间戳。

  2. 设置 Always 拉取策略:在 Deployment 中设置:

    spec:
      template:
        spec:
          containers:
          - name: your-container
            image: your-image:tag
            imagePullPolicy: Always
  3. 对于生产环境:推荐使用前两种方法(添加注释或使用 rollout restart),它们符合 Kubernetes 的声明式设计理念,并且会进行受控的滚动更新。

使用 kubectl rollout restart deployment/[deployment-name] 是最简单且符合 Kubernetes 最佳实践的方式。