Appearance
K3s 安装部署
K3s 常用于资源较小、部署周期短、集群规模不大的 Kubernetes 场景。它保留了 Kubernetes 的 API 和主要资源模型,但把控制平面组件、kubelet、containerd、Flannel、CoreDNS、metrics-server、local-path-provisioner 等内容做了打包,安装和维护入口比 kubeadm 更集中。
K3s 不是另一个容器编排系统,仍然是 Kubernetes。kubectl、Deployment、Service、ConfigMap、Secret、Ingress、Gateway、RBAC 这些对象仍然按 Kubernetes 的方式使用。区别主要在安装方式、默认组件、运行目录和部分内置功能上。
K3s 的常见使用场景:
| 场景 | 说明 |
|---|---|
| 开发测试集群 | 几台虚拟机就能搭出完整 Kubernetes 环境,适合验证资源对象、发布流程和平台组件 |
| 边缘节点 | 资源有限、节点数量不多,集群需要轻量运行 |
| 小规模内部服务 | 内部工具、运维平台、低流量服务可以用 K3s 承载 |
| 云原生组件验证 | Prometheus、Grafana、Loki、Argo CD 等组件需要真实 K8s 对象和标签 |
| 单机或小集群交付 | 安装脚本简单,升级和卸载入口清楚,适合快速交付 |
K3s 和 kubeadm 的区别主要体现在这几块:
| 对比项 | K3s | kubeadm |
|---|---|---|
| 安装方式 | 一个二进制和安装脚本即可拉起集群 | 手动准备 containerd、kubeadm、kubelet、kubectl |
| 默认组件 | 内置 containerd、Flannel、CoreDNS、metrics-server、local-path-provisioner | CNI、metrics-server、存储组件通常单独安装 |
| 状态存储 | 单 server 默认 SQLite,也可以用外部数据库或嵌入式 etcd | 标准控制平面默认 etcd |
| 运行目录 | 主要在 /etc/rancher/k3s、/var/lib/rancher/k3s | 主要在 /etc/kubernetes、/var/lib/kubelet、containerd 目录 |
| 适合场景 | 轻量集群、边缘、小规模服务、快速交付 | 标准自建集群、多控制平面、复杂生产集群 |
环境采用一台 server 加两台 agent。server 节点运行 Kubernetes API Server、调度器、控制器等控制平面组件,同时默认也可以调度业务 Pod;agent 节点主要运行 kubelet、kube-proxy、containerd 和业务 Pod。单 server 架构不具备控制平面高可用,节点故障会影响 API Server 访问。
一、环境规划
机器规划:
| 主机名 | IP | 角色 | 说明 |
|---|---|---|---|
k3s-server01 | 192.168.10.11 | server | 控制平面,同时允许调度 Pod |
k3s-agent01 | 192.168.10.12 | agent | 工作节点 |
k3s-agent02 | 192.168.10.13 | agent | 工作节点 |
安装环境使用 Rocky Linux 9.7。K3s 版本固定为 v1.35.5+k3s1,容器运行时使用 K3s 内置 containerd。
几个默认网段:
| 项目 | 默认值 | 说明 |
|---|---|---|
| Node 网段 | 192.168.10.0/24 | 服务器真实网段 |
| Pod 网段 | 10.42.0.0/16 | K3s 默认 Pod CIDR |
| Service 网段 | 10.43.0.0/16 | K3s 默认 Service CIDR |
| API Server | 192.168.10.11:6443 | agent 加入集群时连接这个地址 |
机器上如果有多个 IP,安装时显式写 --node-ip 更稳。比如 192.168.10.12 同时存在别的地址时,kubelet 自动选择 IP 可能和预期不一致,Service、日志标签、节点访问也容易跟着偏。
二、系统初始化
三台节点都执行。每台机器设置自己的主机名:
bash
# 每台机器按规划设置自己的主机名,避免节点都显示为 localhost.localdomain
hostnamectl set-hostname k3s-server01
# 三台节点保持同一份 hosts,节点名和排错输出会更清楚
cat >/etc/hosts <<'EOF'
127.0.0.1 localhost localhost.localdomain
192.168.10.11 k3s-server01
192.168.10.12 k3s-agent01
192.168.10.13 k3s-agent02
EOF配置时间同步:
bash
# 三台节点使用同一组时间源,避免证书校验、日志时间线和监控数据出现明显偏差
timedatectl set-timezone Asia/Shanghai
cat >/etc/chrony.conf <<'EOF'
server ntp.aliyun.com iburst
server ntp.tencent.com iburst
server cn.pool.ntp.org iburst
sourcedir /run/chrony-dhcp
driftfile /var/lib/chrony/drift
makestep 1.0 -1
rtcsync
keyfile /etc/chrony.keys
ntsdumpdir /var/lib/chrony
leapsectz right/UTC
logdir /var/log/chrony
EOF
systemctl enable --now chronyd
systemctl restart chronyd
# 初次同步时允许 chrony 直接校准一次时间
chronyc -a makestep检查时间同步状态:
bash
timedatectl
chronyc tracking
chronyc sources -vtimedatectl 里 System clock synchronized: yes、NTP service: active 表示系统时钟已经进入同步状态。chronyc sources -v 输出里 ^* 表示当前使用的时间源,^+ 表示可参与组合的候选时间源。makestep 1.0 -1 允许 chrony 在发现时间偏差超过 1 秒时自动跳时,实验环境恢复虚拟机快照后更省事;刚恢复时需要等 chrony 选到可用时间源,Leap status: Normal 后再继续安装或观察监控数据。K8s 里证书、Token、Pod 事件、日志、Prometheus 指标时间戳都依赖节点时间,三台节点时间差太大时,证书校验、事件顺序和监控时间线都可能出现异常。
关闭 swap:
bash
# Kubernetes 调度和资源统计默认按无 swap 环境设计
swapoff -a
# 注释 fstab 里的 swap 行,防止重启后又自动启用
cp -a /etc/fstab /etc/fstab.k3s.bak.$(date +%Y%m%d%H%M%S)
sed -ri '/\sswap\s/s/^/# k3s disabled swap /' /etc/fstab加载内核模块和网络参数:
bash
# overlay 是容器镜像层常用的文件系统模块
# br_netfilter 让 bridge 流量经过 iptables,CNI 和 Service 转发会用到
cat >/etc/modules-load.d/k3s.conf <<'EOF'
br_netfilter
overlay
EOF
modprobe br_netfilter
modprobe overlay
cat >/etc/sysctl.d/99-k3s.conf <<'EOF'
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --systemRocky / CentOS 系列默认可能启用 firewalld。小规模内网环境可以先关闭防火墙,把链路跑通;正式环境更适合按端口逐项放行:
bash
# K3s 会用到 6443、10250、8472/UDP、NodePort 等端口
systemctl disable --now firewalld || true
# SELinux 已经 disabled 的机器会提示 setenforce 不生效,命令失败不影响后续初始化
setenforce 0 || true
if [ -f /etc/selinux/config ]; then
cp -a /etc/selinux/config /etc/selinux/config.k3s.bak.$(date +%Y%m%d%H%M%S)
sed -ri 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config
fiK3s 常用端口:
| 端口 | 协议 | 用途 |
|---|---|---|
6443 | TCP | Kubernetes API Server |
10250 | TCP | kubelet API |
8472 | UDP | Flannel VXLAN |
30000-32767 | TCP/UDP | NodePort |
三、安装 server 节点
在 192.168.10.11 执行。安装脚本使用 Rancher 国内镜像下载 K3s 二进制,版本显式固定:
bash
# 国内环境直接访问 GitHub 经常不稳定,安装脚本从 Rancher 国内镜像拉取
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh -o /root/k3s-install.sh
chmod +x /root/k3s-install.sh
INSTALL_K3S_MIRROR=cn \
INSTALL_K3S_VERSION='v1.35.5+k3s1' \
INSTALL_K3S_EXEC="server \
--node-ip=192.168.10.11 \
--advertise-address=192.168.10.11 \
--write-kubeconfig-mode=644 \
--disable=traefik" \
sh /root/k3s-install.sh几个参数的含义:
| 参数 | 说明 |
|---|---|
INSTALL_K3S_MIRROR=cn | 使用 Rancher 国内镜像下载 K3s 二进制 |
INSTALL_K3S_VERSION | 固定 K3s 版本,避免每次安装拉到不同版本 |
--node-ip | 指定节点注册到集群里的内网 IP |
--advertise-address | API Server 对外通告的地址 |
--write-kubeconfig-mode=644 | 让本机其他用户可以读取 kubeconfig,内网环境操作方便一些 |
--disable=traefik | 不安装内置 Traefik,入口网关单独选择 |
安装完成后看服务状态:
bash
systemctl is-active k3s
kubectl get nodes -o wide
kubectl get pods -A -o wide刚启动的十几秒里,系统 Pod 可能还在 ContainerCreating。节点注册正常以后,再处理镜像源和 agent 加入。
四、配置镜像加速
INSTALL_K3S_MIRROR=cn 只影响 K3s 安装包下载,不代表 Pod 镜像也自动走国内源。K3s 默认系统镜像里有 rancher/mirrored-pause:3.6、rancher/mirrored-coredns-coredns、rancher/mirrored-metrics-server 等,业务应用还会拉 nginx、grafana、loki 这类镜像。国内环境如果不配置 containerd 镜像源,常见现象是节点 Ready,但 Pod 一直卡在 ContainerCreating。
K3s 当前生成的 containerd 配置里使用 certs.d 目录管理 registry hosts:
bash
grep -n "config_path" /var/lib/rancher/k3s/agent/etc/containerd/config.toml三台节点都写入 docker.io 的镜像加速配置:
bash
# K3s 内置 containerd 会读取这个目录下的 hosts.toml
mkdir -p /var/lib/rancher/k3s/agent/etc/containerd/certs.d/docker.io
cat >/var/lib/rancher/k3s/agent/etc/containerd/certs.d/docker.io/hosts.toml <<'EOF'
server = "https://registry-1.docker.io"
[host."https://docker.1ms.run"]
capabilities = ["pull", "resolve"]
EOF重启当前节点上的 K3s 服务。server 节点是 k3s,agent 节点是 k3s-agent:
bash
# server 节点执行
systemctl restart k3s
# agent 节点加入后执行
systemctl restart k3s-agent验证 pause 镜像能拉取:
bash
# pause 镜像拉不下来时,任何 Pod 都创建不了 sandbox
crictl pull docker.io/rancher/mirrored-pause:3.6
crictl images | grep mirrored-pause/etc/rancher/k3s/registries.yaml 是 K3s 官方文档里常见的私有仓库配置入口。实际排查时以节点上的 containerd 生效配置为准:/var/lib/rancher/k3s/agent/etc/containerd/config.toml 里如果指向 certs.d,就检查对应 registry 下有没有 hosts.toml。只写了 YAML 但 containerd 没有加载,Pod 仍然会继续访问 registry-1.docker.io。
五、加入 agent 节点
在 server 节点读取 join token:
bash
cat /var/lib/rancher/k3s/server/node-token在 192.168.10.12 执行:
bash
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh -o /root/k3s-install.sh
chmod +x /root/k3s-install.sh
INSTALL_K3S_MIRROR=cn \
INSTALL_K3S_VERSION='v1.35.5+k3s1' \
K3S_URL='https://192.168.10.11:6443' \
K3S_TOKEN='<server 节点上的 node-token>' \
INSTALL_K3S_EXEC='agent --node-ip=192.168.10.12 --node-name=k3s-agent01' \
sh /root/k3s-install.sh在 192.168.10.13 执行:
bash
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh -o /root/k3s-install.sh
chmod +x /root/k3s-install.sh
INSTALL_K3S_MIRROR=cn \
INSTALL_K3S_VERSION='v1.35.5+k3s1' \
K3S_URL='https://192.168.10.11:6443' \
K3S_TOKEN='<server 节点上的 node-token>' \
INSTALL_K3S_EXEC='agent --node-ip=192.168.10.13 --node-name=k3s-agent02' \
sh /root/k3s-install.shagent 安装完成后,也写入上一节的 hosts.toml 镜像源配置并重启 k3s-agent:
bash
mkdir -p /var/lib/rancher/k3s/agent/etc/containerd/certs.d/docker.io
cat >/var/lib/rancher/k3s/agent/etc/containerd/certs.d/docker.io/hosts.toml <<'EOF'
server = "https://registry-1.docker.io"
[host."https://docker.1ms.run"]
capabilities = ["pull", "resolve"]
EOF
systemctl restart k3s-agent
crictl pull docker.io/rancher/mirrored-pause:3.6回到 server 节点查看三台节点:
bash
kubectl get nodes -o wide正常输出类似:
text
NAME STATUS ROLES VERSION INTERNAL-IP
k3s-agent01 Ready <none> v1.35.5+k3s1 192.168.10.12
k3s-agent02 Ready <none> v1.35.5+k3s1 192.168.10.13
k3s-server01 Ready control-plane v1.35.5+k3s1 192.168.10.11六、验证系统组件
K3s 默认会拉起 CoreDNS、metrics-server 和 local-path-provisioner:
bash
kubectl get pods -A -o wide
kubectl get storageclass
kubectl top nodes几个默认组件的作用:
| 组件 | 作用 | 验证方式 |
|---|---|---|
| CoreDNS | 集群内 DNS,Service 名称解析依赖它 | kubectl -n kube-system get pod -l k8s-app=kube-dns |
| metrics-server | 提供 kubectl top 和 HPA 基础指标 | kubectl top nodes |
| local-path-provisioner | K3s 默认本地存储 provisioner | kubectl get storageclass |
| Flannel | 默认 Pod 网络插件 | Pod 能跨节点通信、节点上有 flannel.1 |
local-path 是节点本地盘,不是共享存储。Pod 调度到不同节点时数据不会自动跟着走,所以它适合临时组件和无状态服务;MySQL、Redis 这类有状态服务更适合单独接 NFS、Ceph、云盘或专门的数据库方案。
七、部署测试服务
部署一个 Nginx 服务,用来确认调度、镜像拉取、Service 转发和 NodePort 访问都正常。
创建命名空间和 Deployment:
bash
kubectl create namespace ops-demo --dry-run=client -o yaml | kubectl apply -f -
# 用 Deployment 创建两个副本,方便观察 Pod 调度到不同节点
kubectl -n ops-demo create deployment web-demo \
--image=nginx:1.27-alpine \
--replicas=2 \
--dry-run=client -o yaml | kubectl apply -f -暴露 NodePort:
bash
# NodePort 会在每台节点上打开一个 30000-32767 范围内的端口
kubectl -n ops-demo expose deployment web-demo \
--port=80 \
--target-port=80 \
--type=NodePort \
--dry-run=client -o yaml | kubectl apply -f -等待发布完成:
bash
kubectl -n ops-demo rollout status deployment/web-demo --timeout=240s
kubectl -n ops-demo get pod -o wide
kubectl -n ops-demo get svc web-demo -o wide取出 NodePort 并从三台节点访问:
bash
NODE_PORT=$(kubectl -n ops-demo get svc web-demo -o jsonpath='{.spec.ports[0].nodePort}')
echo "NODE_PORT=${NODE_PORT}"
# 三台节点任意一个 Node IP + NodePort 都应该能访问到 Service
curl -I --connect-timeout 5 --max-time 10 http://192.168.10.11:${NODE_PORT}
curl -I --connect-timeout 5 --max-time 10 http://192.168.10.12:${NODE_PORT}
curl -I --connect-timeout 5 --max-time 10 http://192.168.10.13:${NODE_PORT}实际 NodePort 以 Service 输出为准。三台节点访问都返回 HTTP/1.1 200 OK 时,说明 NodePort 转发链路正常。日志里能看到来自不同节点转发链路的访问记录:
bash
kubectl -n ops-demo logs deploy/web-demo --tail=5八、常见问题
| 现象 | 排查入口 | 处理方向 |
|---|---|---|
节点是 Ready,Pod 一直 ContainerCreating | kubectl describe pod、journalctl -u k3s -n 100 | 多数是 pause 镜像或业务镜像拉取失败,先验证 crictl pull docker.io/rancher/mirrored-pause:3.6 |
| agent 加入失败 | systemctl status k3s-agent、journalctl -u k3s-agent -n 100 | 看 K3S_URL、K3S_TOKEN、server 6443 端口和时间同步 |
| Pod 没有 IP | kubectl describe pod、节点上的 /run/flannel/subnet.env | 看 Flannel 是否启动、内核模块和 ip_forward 是否正确 |
| NodePort 访问不通 | kubectl get svc -o wide、iptables-save、节点防火墙 | 看 Service 是否有 Endpoints、firewalld 是否拦截、Pod 是否 Ready |
kubectl top nodes 没数据 | kubectl -n kube-system get pod -l k8s-app=metrics-server | metrics-server 未运行、镜像拉取失败或 kubelet 指标访问异常 |
镜像源问题的错误信息通常长这样:
text
failed to get sandbox image "rancher/mirrored-pause:3.6":
failed to pull and unpack image "docker.io/rancher/mirrored-pause:3.6":
failed to do request:
Head "https://registry-1.docker.io/v2/rancher/mirrored-pause/manifests/3.6":
connect: connection refused看到 sandbox image 和 mirrored-pause,问题还没到业务容器镜像那一层,Pod sandbox 都没有创建成功。处理完 pause 镜像以后,如果又出现 nginx、grafana、loki 等镜像拉取失败,再继续处理对应业务镜像仓库。
九、清理重装
K3s 安装脚本会自动生成卸载脚本。server 节点:
bash
# 会删除 K3s 服务、运行目录、内置 containerd 数据和相关网络配置
/usr/local/bin/k3s-uninstall.shagent 节点:
bash
# agent 节点使用独立卸载脚本
/usr/local/bin/k3s-agent-uninstall.sh卸载后可以检查残留目录:
bash
ls /etc/rancher/k3s /var/lib/rancher/k3s /var/lib/kubelet /var/lib/cni 2>/dev/null机器反复重装时,残留的 CNI、iptables、kubelet 数据目录会造成一些看起来很随机的问题。用官方卸载脚本清理,比手工删几个目录更稳。