一、Docker的由来
说到K8S的由来,不得不提到Docker的由来,开发者在自己的环境中开发了一套程序,当他要上线的时候,发现线上的服务器环境,他开发的程序却出现了报错,然后程序员就会说一句,这程序在我电脑上跑的好好的哇,怎么到你那就不行了呢,后来发现,这是由于系统环境引起的,比如这个程序需要安装的依赖版本出现了偏差,环境的组件版本也出现了偏差,才导致了这样的问题。
于是Docker就诞生了,说到Docker的诞生,他的启发来源于货轮,起初货轮在运输的时候,因为物件的不同,搬运工往往要一件一件的搬上床,有的很大要几个人搬,有的很小一个人可以搬好几件,这样就导致货轮的运输和空间效率都很低,于是货轮的集装箱就由此诞生了,不管你有什么物件,全部使用标准集装箱运输,既提高了空间利用率,又提高了帮运效率。这就是为什么Docker的图标像个货轮。

二、K8S的由来
有了Docker后,虽然解决了单个船货运的问题,但是如果是多个船,多个港口,要如何解决调度和部署的问题呢,比如一个货轮坏了,另一个货轮如何让客户快速无感接收坏掉货轮的货品,保障货运的稳定性,于是K8S就诞生了,他解决了多机容器调度、扩容、故障转移的痛点,并可以统一管理海量容器集群。
理解下面几个K8S基础组件,方便后续知道我们为什么要装:
1.containerd
早期为了过度,K8S支持Docker,后续版本就不再支持Docker了,使用了containerd进行了替代,可以理解为容器。

2.kubelet
节点管理员:节点上的"容器管家",所有节点必须安装
3.kubeadm
搭建工具:集群部署的"自动化工具"
4.kubectl
你的指挥工具:集群管理的"命令行客户端"
5.calico
容器网络接口(CNI)插件,解决容器集群的网络连通性和网络安全性问题
三、实验环境
1.准备3台服务器:
下方所有配置,未特别说明在哪台机子上配置的,所有节点都要配置一遍。
(1)test1(Master主节点):
bash
IP:172.18.22.20
NAME:TEST1
作用:K8S的Master主节点
系统:Ubuntu24.04
(2)test2:
bash
IP:172.18.22.21
NAME:TEST2
作用:K8S的分节点
系统:Ubuntu24.04
(3)test3:
bash
IP:172.18.22.22
NAME:TEST3
作用:K8S的分节点
系统:Ubuntu24.04
2.配置HOST,使得直接能通过域名互访:
bash
# 三台服务器都添加HOSTS信息
vim /etc/hosts
172.18.22.20 test1
172.18.22.21 test2
172.18.22.22 test3
3.软件和版本说明
calico :v3.28.0
K8S :v1.28.2
Ubuntu远程工具(可以快速上传文件到服务器):MobaXterm
代码编辑软件:Visual Studio Code
下载calico:Watt Toolkit
4.教程说明
教程很多操作过程,都尽量依赖windows来协助完成,目的是觉得对刚入门的同学比较友好,容易学习和理解,有基础的也可以使用linux的命令来实现,在我觉得只要能达到目的,快的 、容易理解的、顺手的一定是最优解,借助一些软件、系统也不是不可以,不必拘于专业性,那都是花里胡哨装X用的不是。
教程是笔者自学分享,有什么写不好的地方,有大神也可以提出修改意见。
四、安装Docker
1.Docker虽然已经被containerd取代,但我们安装他,是为了我们后续学习时了解原理使用
bash
# 安装说明网址:https://vuepress.mirror.docker-practice.com/install/ubuntu/#%E5%AE%89%E8%A3%85-docker
# 下面两个命令容易受到网络影响,不行就多试几次
# 新手小白,直接用官方提供的脚本,一条命令安装最快,原理是官方打包了Docker必要的安全密钥等信息这样能让新手快速安装
curl -fsSL https://get.docker.com | sh
# 国内可能解析不了上面地址,所以也可以用阿里云镜像来装
curl -fsSL https://get.daocloud.io/docker | sh

五、安装K8S
1.环境配置
(1)关闭交换分区
简单理解就是虚拟内存,把内存信息保存到磁盘上的功能,因为K8S对延时等要求很高,若放置到机械硬盘上,后续可能会出现很多奇怪问题,所以必须关闭。
bash
vim /etc/fstab
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
# / was on /dev/ubuntu-vg/lv-0 during curtin installation
/dev/disk/by-id/dm-uuid-LVM-XPJYtf1S6Ah1o3qM0OVtNOGwz8aHpopZeZQTrSRSVtLpk5u8o2nVowtEnQj2zq2b / ext4 defaults 0 1
# /boot was on /dev/sda2 during curtin installation
/dev/disk/by-uuid/68c92334-a340-456c-ade5-f4ea332a7572 /boot ext4 defaults 0 1
# 注释掉这行
#/swap.img none swap sw 0 0
(2)修改内核模块
bash
# 临时加载模块(Ubuntu 24.04无需提前安装工具,默认有modprobe)
sudo modprobe br_netfilter
# 注释:给小区修「跨楼栋通道」,小区里有很多楼栋(比如「宿主机楼栋」「容器专属楼栋」),住户要串门,得有通道连不同楼栋吧?这个命令就是 加载一个内核模块(相当于给 Linux 系统装个 "小工具"),作用是:让系统能识别「桥接设备」(可以理解成小区里的 "跨楼栋走廊")。K8S 后续会装「网络插件」(比如 Calico、Flannel),这些插件会自动建这个 "跨楼栋走廊",但得先有这个 "小工具" 才能用这个走廊。没有它:不同楼栋的住户(容器)根本没法通信,比如 A 容器在 "1 号楼",B 容器在 "2 号楼",俩容器见不着面。
# 验证加载成功(输出 br_netfilter 即正常)
lsmod | grep br_netfilter
# 创建配置文件(这个文件不存在,一般要自己创建)
vim /etc/sysctl.d/k8s.conf
# 粘贴以下内容
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
# net.bridge.bridge-nf-call-iptables=1 + net.bridge.bridge-nf-call-ip6tables=1:启用用户访问集群的流量分发,及内部容器交换数据流量的综合管理。
# net.ipv4.ip_forward = 1:启用集群网关,
# 永久加载模块让 br_netfilter 内核模块永久生效(开机自动加载)
vim /etc/modules-load.d/k8s.conf
br_netfilter
(3)防火墙(根据情况可跳过)
Ubuntu默认一般时关闭的,如果没有关闭按照下面操作即可,如果关闭了跳过这步骤即可。
bash
# 查看防火墙状态,如果显示Status: inactive,说明防火墙是关闭的;显示Status: active则是开启的。
sudo ufw status
# 关闭防火墙
sudo ufw disable
# 如果要开启防火墙,则需要开启以下端口
# 放开 kube-apiserver 端口 6443
sudo ufw allow 6443/tcp
# 放开 etcd 端口 2379 和 2380
sudo ufw allow 2379/tcp
sudo ufw allow 2380/tcp
# 放开 kube-controller-manager 端口 10257
sudo ufw allow 10257/tcp
# 放开 kube-scheduler 端口 10259
sudo ufw allow 10259/tcp
# 放开 kubelet 端口 10250
sudo ufw allow 10250/tcp
# 放开 NodePort Service 的动态分配端口范围30000-32767
sudo ufw allow 30000:32767/tcp
# 放开 Calico 的端口
sudo ufw allow 179/tcp
sudo ufw allow 4789/udp
sudo ufw allow 5743/tcp
sudo ufw allow 443/udp
# 重新加载防火墙规则
sudo firewall-cmd --reload
(4)时间同步(根据情况可跳过)
K8S对服务器之间的时间要求非常高,如果时间不同步可能引发多重问题,所以建议配置时间同步,如果你的服务器是超融合集群(或者VMware虚拟机),那么没有配置的必要,因为虚拟服务器本身就和超融合校准,双重同步反而容易出现故障。
bash
sudo apt update && sudo apt install -y chrony
sudo vim /etc/chrony/chrony.conf
# 找到所有以 pool 开头的行(比如 pool 2.ubuntu.pool.ntp.org iburst),要么用 # 注释掉(在行首加 #),要么直接删除(按 Delete 键);
# 阿里云时间服务器(优先用,快且稳定)
server ntp.aliyun.com iburst
server time1.aliyun.com iburst
# 华为云时间服务器(备用,防止阿里云故障)
server time1.huaweicloud.com iburst
server time2.huaweicloud.com iburst
# 解释:server 表示 "添加一个时间老师",iburst 表示 "启动时快速同步"(不用等很久)。
# 重启 chrony 服务
sudo systemctl restart chronyd
# 设置开机自启
sudo systemctl enable chronyd
# 快速判断是否同步
timedatectl status
# 显示 System clock synchronized: yes(系统时钟已同步)。
# 查看时间同步状态
chronyc tracking
# 成功的标准(重点看 3 个参数):
# System time:偏差值(比如 +0.000123 seconds),≤0.001 秒(1ms)就是优秀;
# NTP synchronized:显示 yes(表示已经和时间老师同步成功);
# Reference ID:显示某个国内服务器的 IP(比如阿里云的 IP),表示正在用这个老师同步。
# 查看连接的时间服务器
chronyc sources -v
# 成功的标准:
# 有一个服务器前面带 *(表示当前正在同步的主老师);
# 其他服务器前面带 +(表示备用老师,正常连接);
# 没有出现 ?(? 表示连接失败,可能是网络问题)。
2.安装containerd服务
containerd 简单来说,containerd 是一款开源的、轻量级的容器运行时,核心职责是在服务器上管理容器的全生命周期(从镜像拉取、容器创建启动,到停止删除、资源隔离),是连接容器编排平台(如 K8s)和底层操作系统内核的 "桥梁",和 K8s 不是同一家公司的产品,它们是独立开发但紧密协作的开源项目,共同构建了现代容器云生态。
(1)安装containerd
bash
apt install containerd -y
(2)替换配置文件config.toml
安装完成后修改/etc/containerd/中的config.toml文件内容,直接复制内容更换,后续再来理解配置,这边直接照抄就行,主要配置内容就是:
这文件就干了 3 件事:
(a)给管家定好 "工位" 和 "仓库",让它有地方干活;
(b)对接 K8S 的 "需求",让两者配合不吵架;
(c)优化 "拿货渠道" 和 "操作规范",让容器启动快、运行稳。
bash
disabled_plugins = []
imports = []
oom_score = 0
plugin_dir = ""
required_plugins = []
root = "/var/lib/containerd"
state = "/run/containerd"
temp = ""
version = 2
[cgroup]
path = ""
[debug]
address = ""
format = ""
gid = 0
level = ""
uid = 0
[grpc]
address = "/run/containerd/containerd.sock"
gid = 0
max_recv_message_size = 16777216
max_send_message_size = 16777216
tcp_address = ""
tcp_tls_ca = ""
tcp_tls_cert = ""
tcp_tls_key = ""
uid = 0
[metrics]
address = ""
grpc_histogram = false
[plugins]
[plugins."io.containerd.gc.v1.scheduler"]
deletion_threshold = 0
mutation_threshold = 100
pause_threshold = 0.02
schedule_delay = "0s"
startup_delay = "100ms"
[plugins."io.containerd.grpc.v1.cri"]
device_ownership_from_security_context = false
disable_apparmor = false
disable_cgroup = false
disable_hugetlb_controller = true
disable_proc_mount = false
disable_tcp_service = true
enable_selinux = false
enable_tls_streaming = false
enable_unprivileged_icmp = false
enable_unprivileged_ports = false
ignore_image_defined_volumes = false
max_concurrent_downloads = 3
max_container_log_line_size = 16384
netns_mounts_under_state_dir = false
restrict_oom_score_adj = false
sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.7"
selinux_category_range = 1024
stats_collect_period = 10
stream_idle_timeout = "4h0m0s"
stream_server_address = "127.0.0.1"
stream_server_port = "0"
systemd_cgroup = false
tolerate_missing_hugetlb_controller = true
unset_seccomp_profile = ""
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
conf_template = ""
ip_pref = ""
max_conf_num = 1
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
disable_snapshot_annotations = true
discard_unpacked_layers = false
ignore_rdt_not_enabled_errors = false
no_pivot = false
snapshotter = "overlayfs"
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = ""
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime.options]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = ""
CriuImagePath = ""
CriuPath = ""
CriuWorkPath = ""
IoGid = 0
IoUid = 0
NoNewKeyring = false
NoPivotRoot = false
Root = ""
ShimCgroup = ""
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = ""
[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime.options]
[plugins."io.containerd.grpc.v1.cri".image_decryption]
key_model = "node"
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = ""
[plugins."io.containerd.grpc.v1.cri".registry.auths]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.40.62".tls]
insecure_skip_verify = true
[plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.40.62".auth]
username = "admin"
password = "Harbor12345"
[plugins."io.containerd.grpc.v1.cri".registry.headers]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.40.62"]
endpoint = ["https://192.168.40.62:443"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://vh3bm52y.mirror.aliyuncs.com","https://registry.docker-cn.com"]
[plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming]
tls_cert_file = ""
tls_key_file = ""
[plugins."io.containerd.internal.v1.opt"]
path = "/opt/containerd"
[plugins."io.containerd.internal.v1.restart"]
interval = "10s"
[plugins."io.containerd.internal.v1.tracing"]
sampling_ratio = 1.0
service_name = "containerd"
[plugins."io.containerd.metadata.v1.bolt"]
content_sharing_policy = "shared"
[plugins."io.containerd.monitor.v1.cgroups"]
no_prometheus = false
[plugins."io.containerd.runtime.v1.linux"]
no_shim = false
runtime = "runc"
runtime_root = ""
shim = "containerd-shim"
shim_debug = false
[plugins."io.containerd.runtime.v2.task"]
platforms = ["linux/amd64"]
sched_core = false
[plugins."io.containerd.service.v1.diff-service"]
default = ["walking"]
[plugins."io.containerd.service.v1.tasks-service"]
rdt_config_file = ""
[plugins."io.containerd.snapshotter.v1.aufs"]
root_path = ""
[plugins."io.containerd.snapshotter.v1.btrfs"]
root_path = ""
[plugins."io.containerd.snapshotter.v1.devmapper"]
async_remove = false
base_image_size = ""
discard_blocks = false
fs_options = ""
fs_type = ""
pool_name = ""
root_path = ""
[plugins."io.containerd.snapshotter.v1.native"]
root_path = ""
[plugins."io.containerd.snapshotter.v1.overlayfs"]
root_path = ""
upperdir_label = false
[plugins."io.containerd.snapshotter.v1.zfs"]
root_path = ""
[plugins."io.containerd.tracing.processor.v1.otlp"]
endpoint = ""
insecure = false
protocol = ""
[proxy_plugins]
[stream_processors]
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar"]
accepts = ["application/vnd.oci.image.layer.v1.tar+encrypted"]
args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
path = "ctd-decoder"
returns = "application/vnd.oci.image.layer.v1.tar"
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"]
accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"]
args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
path = "ctd-decoder"
returns = "application/vnd.oci.image.layer.v1.tar+gzip"
[timeouts]
"io.containerd.timeout.bolt.open" = "0s"
"io.containerd.timeout.shim.cleanup" = "5s"
"io.containerd.timeout.shim.load" = "5s"
"io.containerd.timeout.shim.shutdown" = "3s"
"io.containerd.timeout.task.state" = "2s"
[ttrpc]
address = ""
gid = 0
uid = 0
(3)启动服务,并开机运行
bash
systemctl start containerd
systemctl enable containerd
3.安装 kubelet、kubeadm、kubectl
kubelet(节点管理员):节点上的"容器管家",所有节点必须安装
kubeadm(搭建工具):集群部署的"自动化工具"
kubectl(你的指挥工具):集群管理的"命令行客户端"
bash
# 用 Ubuntu 官方自带的密钥工具,从 Ubuntu 官方密钥服务器,精准下载并导入「K8s 官方验真密钥」,自动存入系统 "密钥保险柜"
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys B53DC80D13EDEF05
# sudo:用管理员权限执行(后面要写系统文件,普通用户没权限);
# apt-key:Ubuntu 官方内置的「apt 仓库密钥管理工具」
# adv:允许 apt-key 执行「高级操作」这里的 "高级操作" 就是「从远程密钥服务器拉取密钥」(普通操作只能管理本地密钥,高级操作能远程获取)
# --keyserver hkp://keyserver.ubuntu.com:80:指定「拉取密钥的服务器地址 + 端口」
# --recv-keys B53DC80D13EDEF05:精准拉取「指定 "密钥指纹" 对应的密钥」
vim /etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
# 因为第一步已经把apt-key 导入的密钥已存入「系统默认密钥环」,所以这边不需要再指定这个仓库需要引用的密钥路径了。
# deb:仓库类型标识,告诉 apt:这是一个「二进制软件包仓库」(软件是编译好的,下载就能直接安装,不用自己编译)
# https://mirrors.aliyun.com/kubernetes/apt/ → 仓库的 "实际下载地址
# kubernetes-xenial 这个仓库的 "专属名字"
# main 仓库里的 "软件主目录"
# 更新库
apt update
# 查看版本
apt-cache madison kubeadm
# 安装kubelet kubeadm kubectl
apt install -y kubelet kubeadm kubectl
# 安装完成所有服务器都重启下
reboot
# 【拓展】
# 因为Ubuntu24.04官方源没有kubelet kubeadm kubectl二阿里仓库有,所以在安装他们的时候会自动匹配到阿里软件仓库安装
# 如果官方源有,阿里源也有,没有指定的情况下,apt默认会哪个源的版本新,就选哪个,如果版本一样,哪个仓库优先级高选哪个(会按源配置文件的加载顺序)
# 配置规则,指定kubelet kubeadm kubectl从mirrors.aliyun.com下载,配置后,apt会先寻找标记为mirrors.aliyun.com的仓库,也就是kubernetes.list文件中写的下载地址匹配。
sudo vim /etc/apt/preferences.d/kubernetes-pref
Package: kubelet kubeadm kubectl # 只对这三个软件生效
Pin: origin mirrors.aliyun.com # 指定阿里源(origin 是源的域名)
Pin-Priority: 1001 # 优先级(1001 以上是强制优先)
4.K8S的安装配置文件
(1)在Master服务器,生成安装初始化配置文件
bash
# 在K8S的Master服务器上生成K8S的安装配置文件,路径/etc/kubernetes/kubeadm.yaml
kubeadm config print init-defaults > /etc/kubernetes/kubeadm.yaml
(2)配置安装初始化文件
bash
# 修改kubeadm.yaml文件内容,为以下标注地方
vim /etc/kubernetes/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:
# 修改成Master控制节点的服务器IP
advertiseAddress: 172.18.22.20
bindPort: 6443
nodeRegistration:
# find / -name "containerd.sock"查找下文件路径,然后修改下面参数,假设文件路径为/run/containerd/containerd.sock
# 其实就是告诉K8S,containerd得软件交互入口路径Unix 域套接字,为方便理解,你可以把他理解为API接口,这就是为什么containerd已经安装好了,为什么还要告诉Master这个软件路径,因为这个路径不是软件安装位置信息,而是软件得交互入口路径。
criSocket: unix:///run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
# 改成Master的主机名,之前配置得HOSTS信息就起到作用。
name: test1
taints: null
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
# 改成阿里云镜像仓库地址,注意冒号后面要空格。
imageRepository: registry.aliyuncs.com/google_containers
kind: ClusterConfiguration
# 这里指定了安装版本号,如果想安装最新版,注释掉这行即可
kubernetesVersion: 1.28.0
networking:
dnsDomain: cluster.local
# serviceSubnet可以理解为总机号码,用于给外部统一访问集群得一个IP地址入口,也有点类似NAT的公网地址,serviceSubnet、podSubnet、服务器IP必须不同段
serviceSubnet: 10.10.10.0/24
# 容器pod的IP地址,可以理解为分机号,用于给容器之间通讯的地址,类似NAT协议中的私有地址,serviceSubnet、podSubnet、服务器IP必须不同段
podSubnet: 10.10.11.0/24
# 注释或者删除这个POD
# pod
scheduler: {}
# 新增下面两组配置
---
# 定义下载地址
apiVersion: kubeproxy.config.k8s.io/v1alpha1
# 定义这个配置应用于kube-proxy组件
kind: KubeProxyConfiguration
# kube-proxy组件有两种主流转发模式:
# iptables(默认):逐条遍历路由规则,规则越多越耗资源,只能 "随机" 或 "轮询" 转发
# ipvs,把规则存在哈希表里,不管多少规则,都能快速找到,支持多种策略:轮询、最少连接(优先发给空闲 Pod)、哈希等,能根据 Pod 实时负载调整
mode: ipvs
---
# 定义下载地址
apiVersion: kubelet.config.k8s.io/v1beta1
# 定义这个配置应用于Kubelet组件
kind: KubeletConfiguration
# 旧版可能是cgroupfs,资源松散管理弱,配置systemd,起到效果就是保障资源的严格稳定,比如默认配置了1G内存,systemd就是起到不让容器超过1G的效果
cgroupDriver: systemd
下面是非注释版本,直接复制使用,上面的就当理解阅读用,因为注释多了容易报错
bash
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: 172.18.22.20
bindPort: 6443
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: test1
taints: null
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.aliyuncs.com/google_containers
kind: ClusterConfiguration
networking:
dnsDomain: cluster.local
serviceSubnet: 10.10.10.0/24
podSubnet: 10.10.11.0/24
scheduler: {}
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
(4)Master安装初始化K8S
bash
# 在master上执行一键部署集群,使用kubeadm.yaml配置文件,不管 "系统验证类" 的预检通不通,都忽略错误、强行继续初始化集群。
kubeadm init --config=/etc/kubernetes/kubeadm.yaml --ignore-preflight-errors=SystemVerification
# Preflight(预检):kubeadm init 执行前,会自动做一系列 "系统健康检查"(比如服务器内存是否≥2G、swap 是否关闭、内核版本是否达标、端口是否被占用、系统参数是否符合要求等),目的是保证集群能稳定运行;
# SystemVerification(系统验证):是 "预检错误" 的一个类别,包含「服务器硬件 / 系统参数 / 内核版本」等检查(比如内存不足 2G、swap 没关、内核版本略低、文件描述符限制不够等)。
(5)非ROOT用户管理K8S
bash
# 给普通用户建一个 "存放登录密钥的文件夹
mkdir -p $HOME/.kube
# 把root权限的 "集群登录密钥"(admin.conf)复制到普通用户的.kube目录下,并重命名为config
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# 把复制后的config文件的 "所有者" 改成当前普通用户(原本复制过来还是 root 权限,普通用户读不了)
sudo chown $(id -u):$(id -g) $HOME/.kube/config

(6)其他节点安装K8S
(a)生成加入集群的合法校验值
bash
# 在 Master 节点执行,核心作用是:自动生成 Worker 节点加入集群所需的完整 kubeadm join 命令(包含合法的 token + 证书校验值)
kubeadm token create --print-join-command
# 假设生成这样数值,注意token有效时间只有24小时,过期需要重新生成
kubeadm join 172.18.22.20:6443 --token tejs9x.o9sguaimf5m16npi --discovery-token-ca-cert-hash sha256:c003be492cc76e0ee8074c1f046c14a944fc95b7c61fefd7e5175138d0e44d6b
(b)加入节点
bash
# 在test2、test3客户端粘贴生成的校验值并执行,并加入参数 --ignore-preflight-errors=SystemVerification 不管 "系统验证类" 的预检通不通,都忽略错误、强行继续初始化集群。
kubeadm join 172.18.22.20:6443 --token tejs9x.o9sguaimf5m16npi --discovery-token-ca-cert-hash sha256:c003be492cc76e0ee8074c1f046c14a944fc95b7c61fefd7e5175138d0e44d6b --ignore-preflight-errors=SystemVerification

(c)查看集群状态
kubectl get nodes如果直接输入这个命令,系统可能默认去连 localhost:8080(老版本 K8s 的无效地址),而实际集群的 APIServer 地址是 172.18.22.20:6443,我们在部署好kubeadm后就会在kubernetes下生成一个admin.conf文件,这是是一个 YAML 格式的配置文件,本质是 `kubectl` 访问集群的 "精准访问说明书"

bash
# 把配置写入root的bashrc文件最后面加入一条配置export KUBECONFIG=/etc/kubernetes/admin.conf,告诉 kubectl 去哪找 admin.conf的规则,永久固化到 root 用户的终端启动配置里
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> /root/.bashrc
# 立即生效配置(不用重启终端)
source /root/.bashrc
# 验证
kubectl get nodes

5.安装calico
(1)什么是calico
Calico是 Kubernetes(K8s)生态中最主流的容器网络接口(CNI)插件 ,核心作用是解决容器集群的网络连通性 和网络安全性问题,是生产环境中企业级 K8s 集群的首选网络方案之一。K8s 由 Google 开发,Calico 由 Tigera (VMware) 开发,两者都是独立开源项目,通过 CNI 标准接口集成,K8s 负责容器调度,Calico 负责网络连接和安全策略
bash
我们在改vim /etc/sysctl.d/k8s.conf文件时候
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
这些也是网络管理参数,可能有人就会混淆,他和calico的区别:
sysctl 内核参数:节点级、内核层的基础网络开关,负责启用内核的转发 / 桥接规则处理能力,是所有容器网络插件(Calico/Flannel/Cilium)的运行前提;
Calico:集群级、应用层的容器网络解决方案,利用内核的基础能力,实现 Pod 网络连通、IP 管理、精细化网络策略等高级功能。
简单说:sysctl是 "允许做",Calico是 "具体怎么做"------ 二者不是 "谁替代谁",而是 "底层支撑 + 上层实现" 的关系。
(2)calico和K8S的兼容性查询
bash
# 打开网址:
https://docs.tigera.io/calico/latest/getting-started/kubernetes/requirements#kubernetes-compatibility
如下图我们就可以看到calico3.28兼容K8S的1.28版本

(3)下载calico的release包




(4)解压并加载
再自己电脑把release-v3.28.0.tgz用winrar软件解压后,calico-cni.tar、calico-node.tar、calico-kube-controllers.tar通过远程工具上传到3台服务器上即可。


(5)安装组件
(a)解压组件到空间中
bash
# 把calico-cni、calico-node、calico-kube-controllers压缩包,解压后放入到k8s.io空间仓库中,可以理解为windows系统中,有一种绿色程序解压后双击注册,然后再打开软件就能使用,这里的动作就类似把压缩文件拷贝到D盘并且解压。
ctr -n k8s.io images import /root/calico-cni.tar
ctr -n k8s.io images import /root/calico-node.tar
ctr -n k8s.io images import /root/calico-kube-controllers.tar
# kubelet(节点管家),调用containerd(仓库货架)进行创建容器时会优先从k8s.io空间中查找组件,没有才会到网络中查找,这个是硬性约定(底层逻辑)。
# 【拓展】
# 1. 创建test空间命令
ctr namespaces create test
# 导入进去后,之前/root下的calico-cni、calico-node、calico-kube-controllers这三个包基本就没用了可以删除
sudo rm /root/calico-cni.tar
# 批量删除(三个tar包一起删)
sudo rm /root/calico-cni.tar /root/calico-node.tar /root/calico-kube-controllers.tar
成功导入示意图:

(b)3台服务器查网卡
bash
ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
link/ether 00:0c:29:b3:27:f1 brd ff:ff:ff:ff:ff:ff
altname enp2s1
inet 172.18.22.20/24 brd 172.18.22.255 scope global ens33
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:feb3:27f1/64 scope link
valid_lft forever preferred_lft forever
3: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
link/ether e6:d6:79:42:77:e4 brd ff:ff:ff:ff:ff:ff
inet 10.10.10.10/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 10.10.10.1/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
# 这里可以看出我们的IP地址172.18.22.20对应的网卡是ens33,我这边服务器都是ens33,所以就不在一一描述
(c)修改安装说明书calico.yaml
calico.yaml,其实类似windows的".reg"注册表文件,告诉系统怎么使用或调用calico的相关组件。
bash
# 增加下面两行参数
- name: IP_AUTODETECTION_METHOD
value: "interface=ens33"
# 变量名(IP自动检测规则):IP_AUTODETECTION_METHOD
# 指定网卡:ens33
# 核心作用是强制 Calico 节点(calico-node)使用服务器上名为 ens33 的网卡 IP 作为集群内的节点通信 IP ------ 解决多网卡场景下 Calico 自动选错网卡导致的跨节点网络不通问题。

(d)3台服务器都执行安装calico命令(类似windows的注册组件)
bash
# 进入到calico.yaml所在目录/root后,输入安装指令,类似windows的注册calico组件动作。
kubectl apply -f calico.yaml
# kubectl:K8s 集群的命令行操作工具
# apply:应用配置
# -f:指定配置文件位置,没写则默认当前目录
# calico.yaml:配置文件名
(e)状态查询
bash
# 当3个服务器都安装完成后,在Master test1运行下面命令,可以查看部署情况,如果都是running则表示部署成功
kubectl get pods -n kube-system -o wide | grep calico
# kubectl:K8s 集群的命令行操作工具
# get pods:获取集群中所有 Pod 资源的列表
# -n kube-system:K8s 把核心组件(Calico、kube-proxy、coredns 等)都放在 kube-system 命名空间,不加这个参数看不到 Calico Pod
# -o wide:默认输出只有 Pod 名、状态、运行时长,-o wide 是排查节点级问题的关键,会多输出NODE(运行节点)、IP(Pod 的 IP 地址)、RESTARTS(Pod 重启次数)
# | grep calico:grep 只保留NAME列中含 "calico" 的行避免信息杂乱
# 输出信息:
calico-kube-controllers-8d76c5f9b-h2tlp 1/1 Running 0 53m 10.10.11.131 test1 <none> <none>
calico-node-8nxdz 1/1 Running 0 53m 172.18.22.21 test2 <none> <none>
calico-node-cvtng 1/1 Running 0 53m 172.18.22.20 test1 <none> <none>
calico-node-xdbhk 1/1 Running 0 53m 172.18.22.22 test3 <none> <none>
# kubectl get nodes也会从NotReady变成 Ready
kubectl get nodes
NAME STATUS ROLES AGE VERSION
test1 Ready control-plane 7d23h v1.28.2
test2 Ready <none> 7d23h v1.28.2
test3 Ready <none> 7d23h v1.28.2
正常状态:
| 状态 | 出现场景 | 核心含义 | 处理 |
|---|---|---|---|
| Init:0/3Init:1/3Init:2/3 | 刚执行 kubectl apply -f calico.yaml 后 |
calico-node 正在执行初始化容器(安装 CNI、配置网络),数字是 "已完成的初始化容器数 / 总数" | 等待即可,通常 1~2 分钟内会推进 |
| Init:3/3 | 初始化容器全部执行完 | 所有初始化步骤完成,即将启动 calico-node 主容器 | 短期过渡,会自动切到 ContainerCreating |
| ContainerCreating | Init:3/3 之后 |
K8s 正在为 calico-node 主容器分配网络、挂载卷 | 等待即可,正常持续 10~30 秒 |
| 1/1 Running | 部署成功后 | Calico 容器(calico-node/calico-kube-controllers)完全就绪并运行,集群网络正常 | 无需处理,这是目标状态 |
Pending(短期,<1 分钟) |
calico-kube-controllers 刚创建时 | 控制器 Pod 正在等待 K8s 调度到节点 | 等待调度完成即可 |
Terminating(短期,<1 分钟) |
升级 / 删除 Calico Pod 时 | Pod 正在正常终止(旧 Pod 退出,新 Pod 启动) | 等待自动完成(DaemonSet 升级时常见) |
异常状态:
| 状态 | 出现场景 | 核心含义 | 处理 |
|---|---|---|---|
| Init:ImagePullBackOff | 部署后 Pod 一直卡这个状态 | 镜像拉取失败(节点未导入 Calico 镜像、版本不匹配、镜像在非 k8s.io 空间) | 1.去 NODE 列对应的节点导入镜像;2. 核对 yaml 镜像版本与导入的一致;3. 确认镜像导入到 k8s.io 空间 |
| Init:ErrImagePull | Init:ImagePullBackOff 之前 |
镜像拉取 "即时失败"(版本不存在、名称写错、私有仓库认证失败) | 先核对镜像名称 / 版本,再按ImagePullBackOff处理 |
| Init:CrashLoopBackOff | 镜像拉取成功后 | 初始化容器执行失败(权限不足、CNI 目录无法写入、etcd 连通失败) | 1.kubectl describe pod Pod名 -n kube-system 看 Events;2. 检查节点 /etc/cni/net.d 目录权限;3. 验证 etcd 连通性 |
CrashLoopBackOff(无 Init 前缀) |
calico-node 主容器启动后 | 主进程崩溃(网段冲突、内核参数未开 ip_forward、etcd 地址写错) | 1.查看 Pod 日志:kubectl logs Pod名 -n kube-system;2. 检查节点内核参数:sysctl net.ipv4.ip_forward;3. 核对 Calico 配置(网段、etcd) |
| Error | 异常状态重试多次后 | 初始化 / 主容器失败,K8s 停止重试(比如镜像拉取失败次数耗尽、进程崩溃次数超限) | 先解决底层问题(镜像 / 配置 / 权限),再删除 Pod 触发重建:kubectl delete pod Pod名 -n kube-system |
Pending(长期,>5 分钟) |
calico-kube-controllers 一直卡这个状态 | 所有节点的 calico-node 未就绪,控制器 Pod 无可用节点调度 | 先解决 calico-node 的异常(比如 ImagePullBackOff),控制器会自动调度 |
如果到这里,你的显示都是Running,那么恭喜你,你的K8S入门部署就算完成啦!