月度TODO

  • 极限科技 4篇文章
  • 懒猫微服 4篇文章
  • 生活感悟1篇
  • 纯技术文章 2篇
  • 英语

在AWS EC2 上从零搭建 Kubernetes 集群(kubeadm)

今天讲解在AWS EC2 上使用kubeadm搭建Kubernetes 集群。

kubeadm 是 Kubernetes 官方提供的集群引导工具,用来快速创建符合最佳实践的 K8s 集群。除了初始化集群,它还能做节点的升级、降级等生命周期管理。用 kubeadm 建集群是学习 K8s 的推荐方式,也适合搭建小规模集群或作为更复杂企业级方案的基础组件。

本文基于 Ubuntu,使用三台 EC2 实例:一台作为控制面(Master),两台作为工作节点(Worker)。

我们会在 Master 节点上从头安装 kubeadm 及其依赖,然后初始化集群,最后把 Worker 节点加入进来。

这几台 EC2 使用同一个安全组,入站规则只放行VPC网段和终端访问地址。

第一步:安装 kubeadm 及其依赖

以下操作在 Master 节点上执行(Worker 节点如果还没装也要跑一遍)。

1. 关闭 swap

kubelet 需要精确管理 Pod 的内存,swap 会让内存数据不准确,导致调度器做出错误判断。kubelet 默认检测到 swap 开着就拒绝启动。

1
2
3
4
# 关闭 swap
sudo swapoff -a
# 注释掉 fstab 里的 swap 行,重启后也不会再开
sudo sed -i '/ swap / s/^/#/' /etc/fstab

验证:free -h,Swap 行全是 0 就对了。

1
2
3
4
5
ubuntu@ip-172-31-27-240:~$ free -h
total used free shared buff/cache available
Mem: 1.9Gi 361Mi 1.4Gi 2.7Mi 303Mi 1.5Gi
Swap: 0B 0B 0B
ubuntu@ip-172-31-27-240:~$

2. 更新包管理器,安装基础依赖

更新 apt 包索引,安装通过 HTTPS 访问软件仓库所需的包:

1
2
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release

3. 加载内核模块,允许 IPv4 转发

加载 overlaybr_netfilter 两个内核模块。overlay 是 containerd 存储驱动需要的,br_netfilter 让 bridge 上的流量能经过 iptables 规则,K8s 的 Service 和 NetworkPolicy 都依赖这个。

1
2
3
4
5
6
7
sudo modprobe overlay
sudo modprobe br_netfilter

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

4. 设置内核网络参数

让 Linux 节点的 iptables 能正确处理桥接流量。三个参数缺一不可:

  • bridge-nf-call-iptables:bridge 流量过 iptables,Service 转发靠这个
  • ip_forward:开启 IP 转发,Pod 跨节点通信需要
  • bridge-nf-call-ip6tables:同上,IPv6 版本
1
2
3
4
5
6
7
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

sudo sysctl --system

5. 安装 containerd

使用 Docker 官方分发的 DEB 包安装 containerd 作为容器运行时。这只是安装 containerd 的方式之一,也可以从源码编译或用其他包管理器。

1
2
3
4
5
6
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y containerd.io

6. 配置 systemd cgroup 驱动

生成 containerd 默认配置,然后把 cgroup 驱动改成 systemd。这一步是为了避免系统里同时存在两个 cgroup 管理器(systemd 和 cgroupfs)导致的不稳定问题。kubelet 默认用 systemd,containerd 也要对齐。

1
2
3
4
5
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd

7. 安装 kubeadm、kubelet、kubectl

从 Kubernetes 官方包仓库安装。三个组件的作用:

  • kubeadm:集群引导工具,负责 init 和 join
  • kubelet:每个节点上的代理,负责管理 Pod 和容器
  • kubectl:命令行工具,用来操作集群

下面以 v1.30 为例。如果要装其他版本(比如 v1.35),把两处 v1.30 都改成 v1.35 就行,GPG key 和源地址的版本号要一致。可用版本列表见 https://pkgs.k8s.io/core:/stable:/

1
2
3
4
5
6
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl

8. 锁定版本,防止自动升级

K8s 组件版本需要严格对齐,自动升级可能导致版本不一致出问题:

1
sudo apt-mark hold kubelet kubeadm kubectl

第二步:初始化控制面节点

以下操作仅在 Master 节点执行。

初始化过程会完成以下事情:

  • 创建证书颁发机构(CA),用于集群内的安全通信和身份认证
  • 启动节点组件:kubelet
  • 启动控制面组件:API Server、Controller Manager、Scheduler、etcd
  • 安装通用插件:kube-proxy、DNS

kubeadm 初始化使用合理的默认值,遵循最佳实践。当然也有很多配置选项可以自定义,比如使用自己的 CA 证书或外部 etcd 存储。

kubeadm 不会帮你安装网络插件,这个需要自己装。我们用 Calico 作为 Pod 网络插件。Calico 支持 Kubernetes NetworkPolicy,也是 AWS、Azure、GCP 的托管 K8s 服务内部使用的网络方案,生产可用。为了让 Calico 的网络策略正常工作,初始化时必须通过 --pod-network-cidr 指定 Pod 网络的 IP 范围。

1. 初始化控制面

1
sudo kubeadm init --pod-network-cidr=192.168.0.0/16 

192.168.0.0/16 是 Calico 的默认网段。注意这个网段不能跟你的 VPC 网络 CIDR 重叠,如果重叠了需要额外配置 Calico 来避免冲突。

输出会显示 kubeadm 初始化控制面的每一步。最后会给出两个重要信息:配置 kubectl 的命令和 worker 节点加入集群的 join 命令。

2. 保存 join 命令

把输出最后的 kubeadm join ... 命令复制下来存好,后面 worker 节点加入集群要用。token 默认 24 小时过期,过期了可以用 kubeadm token create --print-join-command 重新生成。

3. 配置 kubectl

用 kubeadm 生成的 admin kubeconfig 初始化 kubectl 配置:

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

4. 确认控制面组件状态

1
kubectl get componentstatuses

输出应该显示 scheduler、controller-manager、etcd 都是 Healthy。kubectl 能正常返回结果也说明 API Server 在正常工作。

5. 查看节点状态

1
kubectl get nodes

这时候控制面节点会显示 NotReady。这是正常的——因为还没装网络插件。

1
2
3
NAME               STATUS     ROLES           AGE     VERSION
ip-172-31-16-97 NotReady <none> 33s v1.30.14
ip-172-31-27-240 NotReady control-plane 4m11s v1.30.14

可以用 kubectl describe nodes 看详细信息,Conditions 里会显示 Ready: False,原因是 “network plugin is not ready”,CNI(Container Network Interface)还没初始化。

6. 安装 Calico 网络插件(Operator 方式)

Calico 有两种安装方式:Operator 和 Manifest。官方现在推荐 Operator 方式,通过 Tigera Operator 来管理 Calico 的生命周期,后续升级和配置变更都更方便。

先装 Tigera Operator:

1
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.0/manifests/tigera-operator.yaml

再装 Calico 自定义资源:

1
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.0/manifests/custom-resources.yaml

custom-resources.yaml 里默认的 Pod CIDR 是 192.168.0.0/16,跟 kubeadm init 时指定的一致。如果你用了不同的 CIDR,需要先下载这个文件改 cidr 字段再 apply。

这会创建一系列资源来支持 Pod 网络,包括:

  • Tigera Operator Deployment,负责管理 Calico 组件的部署和升级
  • 一个 DaemonSet,在集群每个节点上运行一个 calico-node Pod
  • 多个 CRD(自定义资源定义),扩展 K8s API 来支持网络策略等功能

Calico 组件会部署在 calico-system namespace 下(Operator 方式),而不是 kube-system(Manifest 方式)。

7. 等待节点就绪

1
watch kubectl get nodes
1
2
3
4

NAME STATUS ROLES AGE VERSION
ip-172-31-16-97 Ready <none> 95s v1.30.14
ip-172-31-27-240 Ready control-plane 5m13s v1.30.14

网络插件初始化完成后,控制面节点会变成 Ready。可能需要等一分钟左右。按 Ctrl+C 停止 watch。


第三步:Worker 节点加入集群

以下操作在 Worker 节点执行。

使用 kubeadm 添加工作节点比初始化控制面还要简单,一条 join 命令就搞定。

1. 连接到 Worker 节点

打开一个新终端,SSH 连接到 Worker 节点(用户名 ubuntu)。

2. 执行 join 命令

sudo 加上之前从 kubeadm init 输出中保存的 join 命令:

1
2
3
sudo kubeadm join 10.0.0.100:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash>

如果 token 过期了(24 小时有效),在 Master 上重新生成:

1
kubeadm token create --print-join-command

3. 确认 Worker 节点加入集群

回到控制面节点的终端,查看节点状态:

1
kubectl get nodes

Worker 节点会显示出来,ROLES 列为 <none>(这是正常的,worker 节点默认没有角色标签)。节点可能需要一分钟左右才能变成 Ready。

4. 确认所有 Pod 正常运行

1
kubectl get pods --all-namespaces

所有 Pod 都应该处于 Running 状态。注意每个节点上都会有 Calico 的 Pod 在运行,负责该节点的网络功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ubuntu@ip-172-31-27-240:~$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
calico-apiserver calico-apiserver-7458595b77-jclzd 1/1 Running 0 61s
calico-apiserver calico-apiserver-7458595b77-w2ww8 1/1 Running 0 61s
calico-system calico-kube-controllers-8b7bdfdc7-tgx2p 1/1 Running 0 60s
calico-system calico-node-d4m6v 1/1 Running 0 61s
calico-system calico-node-lr6bq 1/1 Running 0 61s
calico-system calico-typha-5494555c85-5vftk 1/1 Running 0 61s
calico-system csi-node-driver-spgw8 2/2 Running 0 60s
calico-system csi-node-driver-xjsfk 2/2 Running 0 60s
kube-system coredns-55cb58b774-fjhvm 1/1 Running 0 5m28s
kube-system coredns-55cb58b774-z4c6x 1/1 Running 0 5m28s
kube-system etcd-ip-172-31-27-240 1/1 Running 0 5m41s
kube-system kube-apiserver-ip-172-31-27-240 1/1 Running 0 5m42s
kube-system kube-controller-manager-ip-172-31-27-240 1/1 Running 0 5m41s
kube-system kube-proxy-424ck 1/1 Running 0 5m28s
kube-system kube-proxy-7tpcs 1/1 Running 0 2m7s
kube-system kube-scheduler-ip-172-31-27-240 1/1 Running 0 5m41s
tigera-operator tigera-operator-5645cfc98-fjxtp 1/1 Running 0 67s

双节点集群到这里就搭建完成了。

第四步:部署应用验证集群

集群搭好了,跑个 nginx 验证一下。

1. 创建 nginx Pod

1
kubectl run nginx --image=nginx --port=80

2. 确认 Pod 运行正常

1
kubectl get pods -o wide
1
2
3
ubuntu@ip-172-31-27-240:~$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 9s 192.168.98.135 ip-172-31-16-97 <none> <none>

看 STATUS 是 Running,IP 已分配,记下 Pod 跑在哪个节点上。

3. 通过 NodePort 暴露服务

1
2
kubectl expose pod nginx --type=NodePort --port=80
kubectl get svc nginx

输出里 PORT(S) 列会显示类似 80:31973/TCP31973 就是 NodePort。

4. 访问 nginx

1
2
# 用 Pod 所在节点的 IP + NodePort
curl <worker节点IP>:<NodePort>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ubuntu@ip-172-31-27-240:~$ curl 172.31.16.97:32686
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

能看到 nginx 欢迎页就说明集群网络正常。

注意:NodePort 通过节点 IP 访问,不是 ClusterIP。ClusterIP 是集群内部的虚拟 IP,只在集群内部可达。

AWS 上的 Calico 网络踩坑

在 AWS EC2 上用 Calico 默认的 BGP 路由模式,你可能会发现从一个节点直接 curl 另一个节点上的 Pod IP 不通。比如从 master(10.0.0.100)curl worker 上的 Pod(192.168.180.194)会超时。

路由表是对的(ip route | grep 192.168 能看到到 worker Pod 网段的路由),但包就是过不去。

原因是 AWS VPC 的源/目标检查(Source/Destination Check)。EC2 默认会检查每个网络包的源 IP 和目标 IP 是否属于这台实例,Pod IP(192.168.x.x)不属于 EC2 的 VPC 地址,VPC 直接把包丢了。

关闭源/目标检查(简单)

AWS Console → EC2 → 选中实例 → Actions → Networking → Change source/destination check → Stop

所有节点都要改。改完后跨节点的 Pod IP 直接访问就通了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ubuntu@ip-172-31-27-240:~$ curl 192.168.98.135
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

第五步:用 Deployment 管理副本

前面用 kubectl run 创建的是裸 Pod,不支持扩缩容。生产环境应该用 Deployment,支持副本管理和滚动更新。

1. 删掉裸 Pod,改用 Deployment

1
2
3
4
5
6
kubectl delete pod nginx
kubectl delete svc nginx

# 创建 Deployment,10 个副本
kubectl create deployment nginx --image=nginx --replicas=10
kubectl expose deployment nginx --type=NodePort --port=80

2. 查看副本状态

1
2
kubectl get deployment nginx
kubectl get pods -o wide

10 个 Pod 会分布在不同节点上。

3. 验证所有 Pod 都能访问

一行命令逐个 curl 所有 Pod IP:

1
kubectl get pods -l app=nginx -o jsonpath='{range .items[*]}{.status.podIP}{"\n"}{end}' | while read ip; do echo "--- $ip ---"; curl --connect-timeout 3 -s $ip | head -1; done

每个 IP 都输出 <!DOCTYPE html> 就说明都通了。

4. 调整副本数

1
2
3
4
5
# 缩到 3 个
kubectl scale deployment nginx --replicas=3

# 扩到 20 个
kubectl scale deployment nginx --replicas=20

附:安装 k9s(终端集群管理工具)

k9s 是一个终端 UI 工具,比 kubectl 看集群状态直观很多,支持实时查看 Pod、日志、资源占用等。

Mac:

1
brew install derailed/k9s/k9s

Ubuntu/Linux:

1
curl -sS https://webi.sh/k9s | sh

或者直接下载二进制:

1
2
3
curl -LO https://github.com/derailed/k9s/releases/latest/download/k9s_Linux_amd64.tar.gz
tar xzf k9s_Linux_amd64.tar.gz
sudo mv k9s /usr/local/bin/

装完敲 k9s 进入,按 : 输入资源类型(比如 podsdeploysvc)切换视图从而看到集群的各种信息,q 退出。

修复 GitHub Pages 推送后 CNAME 自动重置旧域名的问题

部署后 GitHub Pages 域名自动变成已经取消的实效的域名,而不是预期的 *.github.io

排查之后是source/CNAME 文件中配置了旧域名 airag.click

删除 CNAME 文件后重新部署就可以解决

1
rm source/CNAME
  • CNAME 文件会被 Hexo 复制到生成目录,告诉 GitHub Pages 使用自定义域名
  • 删除后将使用默认的 <username>.github.io 域名

k3s + Helm 部署 Easysearch

最近学了K8S,为了测试方便测试搭了一个K3S集群,然后使用helm运行一下Easysearch。

参考文档:https://docs.infinilabs.com/easysearch/main/docs/deployment/install-guide/helm/

首先添加helm仓库并更新。

1
2
helm repo add infinilabs https://helm.infinilabs.com
helm repo update

然后新建命名空间,我这里叫做es(下同),也可以使用其他名字。

1
kubectl create namespace es
阅读更多

懒猫微服实战入门(三十六):懒猫微服QEMU虚拟机快速上手

对于 NAS 玩家来说,虚拟机绝对是标配。今天我们要介绍的主角是 QEMU。你可能会觉得它太过底层、全命令行操作太硬核,但别担心,看过这篇文章之后,你就能轻松在懒猫微服上操作它。

在传统 Linux 下装 QEMU,你可能要折腾一堆 kvm-ok 检测、各种动态库依赖。但在懒猫微服上,直接从商店下载即可。这就是全容器化的好处:环境全封闭,不会把宿主机的依赖搞坏,不用再和底层依赖打交道,这就是懒猫微服全容器化的好处,彻底解决了让人头疼的环境问题。

image-20260226215103173

阅读更多

Easysearch 数据映射之 Deep Dive:我踩过的 Volume 坑

最近在用 Docker 部署 Easysearch,本以为是个简单的事情,结果在数据持久化上栽了跟头,每次停止再启动容器之后都会503,在后面成了我百思不得其解的问题,后来一直在某次的meetup中,请教了原厂的罗老师,一句话点醒梦中人,Easysearch用的具名卷,防止宿主机的数据覆盖容器里的数据。

阅读更多

超效率手册

  1. 周/日 计划

  2. 限制时间做事(30-90min) 紧迫感、

  3. 任务分解

  4. 短跑。起床坚持10分钟,其他事再坚持20分钟休息。 短跑30天习惯

  5. 日程校对,相信自己要做的事情。完全日程,避免过度工作和拖延。

  6. 自律。停下来之前再坚持10分钟。下次做事再坚持10-20%,专注一件事

  7. 语录刺激生产效率(便利贴

阅读更多

给Macbook Pro安装Fedora Asahi Remix

16G的Macbook 经常出问题,应该是内存太小吧,所以安装一个Linux做双系统吧,Asahi搞定UEFI这层,本质上还是Fedora。

655cadefb7a4b564f74c0f5e98948e51

起码目前阶段对我的M2 Macbook 兼容还不错,打算尝尝鲜。

阅读更多

安卓ADB提取APK安装包

之前的文章写了如何使用无线调试ADB,那么我们就可以用adb提取安装包了。

1
adb shell pm list packages

这里会列出很多app的包名,如果你知道叫啥名字页可以grep过滤一下。

然后,可以用这个命令看包名的地址。会给一个地址

1
adb shell pm path <包名>

然后使用adb pull这个地址,就可以在当前路径得到apk。

1
adb pull <地址>

记一次硬盘满了导致 Coco Server 无法启动的排查

这次排查非常典型:容器日志看起来像“卡在某一行”,Easysearch 甚至已经启动,但 Coco Server 的进程并没有真正跑起来。

最后发现根因是:磁盘剩余空间不足(< 5GB)触发 coco 自检阈值,直接 panic 退出,造成了“日志一直卡住、服务一直起不来”的假象,差不多排查了半个多小时。

背景:目标与现象

目标是在 RK3566 / Armbian 上把 cocoai-arm:test 跑起来,并通过宿主机端口访问服务:

  • 宿主机映射:-p 9000:9000

  • 容器挂载 volume:

    • coco_data_vol:/app/easysearch/data
    • coco_config_vol:/app/easysearch/config
    • coco_logs_vol:/app/easysearch/logs
阅读更多
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×