0%

基于 Kind 搭建 k8s 开发环境

这篇文章将学习如何使用 Kind(Kubernetes IN Docker)来搭建一个本地 k8s 环境,其实之前在配置 cilium 环境的时候就使用过 Kind,而这篇文章我们将会更详细地学习 Kind 的使用,并在此基础上搭建一个完整的 k8s 环境。

Kind 环境安装

Kubernetes (k8s) 是一个容器编排平台,用于自动化容器的部署、扩展和管理。Kind (Kubernetes in Docker) 是用 Docker 容器模拟 k8s 节点的工具,每个节点 实际上是一个 Docker 容器,容器内运行着完整的 K8s 组件。通过 Kind,可以适合快速搭建本地开发、测试和学习的 k8s 环境。

系统信息

  • 如下是我当前的 Linux 服务器信息(单台服务器)
项目 信息
系统 Ubuntu 22.04.5 LTS
内核 5.15.0-40-generic
CPU 4核
内存 15Gi
Docker 28.5.1
  • 由于 Kind 依赖于 Docker 来运行 k8s 节点,因此首先需要确保 Docker 已经安装并运行正常
1
2
docker info --format '{{.ServerVersion}}'
docker ps -q | head -1

安装 Kind

1
2
curl -sLo /usr/local/sbin/kind "https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64"
chmod +x /usr/local/sbin/kind
1
2
# kind version
kind v0.31.0 go1.25.5 linux/amd64
  • 这里直接安装 Kind 的最新稳定二进制版本
  • 官网也提供了其他安装方式,例如通过包管理器安装或者源码安装

2.2 安装 Kubectl

1
2
curl -sLo /usr/local/sbin/kubectl "https://dl.k8s.io/release/v1.35.0/bin/linux/amd64/kubectl"
chmod +x /usr/local/sbin/kubectl
1
2
3
4
# kubectl version
Client Version: v1.35.0
Kustomize Version: v5.7.1
Server Version: v1.35.0
  • kubectl 是 Kubernetes 的命令行工具,kind 本身不需要 kubectl,但是为了方便管理集群和资源,建议安装 kubectl
  • kubectl 用于与 K8s API Server 交互,管理集群资源
  • Kustomize 是 K8s 原生的配置管理工具,内置于 kubectl
  • kubectl 的官方安装文档,可以参考这里
  • 这里我们安装的 kubectl 版本和 k8s 版本都是最新的稳定版本

3. 创建集群

3.1 编写集群配置文件

创建文件 kind-config.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: k8s-cluster
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
protocol: TCP
- containerPort: 30001
hostPort: 30001
protocol: TCP
- containerPort: 30002
hostPort: 30002
protocol: TCP
- role: worker
- role: worker
networking:
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/12"

配置详解

配置项 说明
kind: Cluster 声明这是一个 Kind 集群配置
apiVersion: kind.x-k8s.io/v1alpha4 Kind 配置的 API 版本
name: k8s-cluster 集群名称,用于标识和切换上下文
nodes 定义集群节点列表
role: control-plane 控制面节点,运行 K8s 管理组件(API Server、Controller、Scheduler、etcd)
role: worker 工作节点,运行用户的应用 Pod
extraPortMappings 端口映射,将容器端口映射到宿主机,便于访问 NodePort 服务
podSubnet Pod 网络地址范围,每个 Pod 会获得该范围内的 IP
serviceSubnet Service 网络地址范围,每个 Service 会获得虚拟 IP(ClusterIP)

K8s 节点角色说明

角色 功能 类比
control-plane 集群大脑,管理调度、存储状态、响应 API 请求 公司管理层
worker 执行者,运行实际的应用容器 公司员工

3.2 创建集群

1
kind create cluster --config kind-config.yaml --wait 300s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Creating cluster "k8s-cluster" ...
• Ensuring node image (kindest/node:v1.35.0) 🖼 ...
✓ Ensuring node image (kindest/node:v1.35.0) 🖼
• Preparing nodes 📦 📦 📦 ...
✓ Preparing nodes 📦 📦 📦
• Writing configuration 📜 ...
✓ Writing configuration 📜
• Starting control-plane 🕹️ ...
✓ Starting control-plane 🕹️
• Installing CNI 🔌 ...
✓ Installing CNI 🔌
• Installing StorageClass 💾 ...
✓ Installing StorageClass 💾
• Joining worker nodes 🚜 ...
✓ Joining worker nodes 🚜
• Waiting ≤ 5m0s for control-plane = Ready ⏳ ...
✓ Waiting ≤ 5m0s for control-plane = Ready ⏳
• Ready after 12s 💚
Set kubectl context to "kind-k8s-cluster"

步骤解释

步骤 说明
Ensuring node image 下载 Kind 的 K8s 节点镜像(包含完整 K8s 组件)
Preparing nodes 创建 3 个 Docker 容器作为 K8s 节点
Writing configuration 写入集群配置(证书、kubeconfig 等)
Starting control-plane 启动控制面组件(API Server、etcd、Controller、Scheduler)
Installing CNI 安装容器网络接口(Kind 默认使用 kindnet),让 Pod 可以互相通信
Installing StorageClass 安装默认存储类(local-path),支持持久化存储
Joining worker nodes 让 worker 节点加入集群,建立与控制面的连接
Waiting for Ready 等待控制面节点状态变为 Ready

关键概念解释

组件 功能
CNI (Container Network Interface) Pod 网络插件,负责 Pod 之间的网络通信。每个 Pod 都有自己的 IP,CNI 让它们 能互相访问
StorageClass 存储类型定义,告诉 K8s 如何创建持久化存储卷。应用数据需要持久化时,K8s 会根据 StorageClass 自动创建存储
kubeconfig 认证配置文件,包含集群地址、证书、用户信息,kubectl 用它连接集群

4. 验证集群状态

在创建集群之后,就可以使用 kubectl 命令来和 k8s 集群进行交互了。kubectl 会自动使用 KIND 所生成的 kubeconfig 文件 来连接集群,该配置文件默认保存在 ${HOME}/.kube/config 目录下。kubeconfig 文件告诉 kubectl:我要连接哪个集群?用什么身份连接?以及如何验证安全连接?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://127.0.0.1:44199
name: kind-k8s-cluster
contexts:
- context:
cluster: kind-k8s-cluster
user: kind-k8s-cluster
name: kind-k8s-cluster
current-context: kind-k8s-cluster
kind: Config
users:
- name: kind-k8s-cluster
user:
client-certificate-data: DATA+OMITTED
client-key-data: DATA+OMITTED
  • clusters 字段:集群信息,包括证书和 API 服务器地址,通过 kubectl 命令和集群交互时,就是往这个 API 服务器发送请求
  • users:用户信息,包括客户端证书和密钥,用于认证和授权
  • contexts:当前使用的上下文,即当前连接的集群和用户,实现一个逻辑绑定,将用户和集群关联起来
  • current-context:默认的上下文,执行 kubectl 时会自动使用这个上下文
1
2
3
4
5
# kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:44199
CoreDNS is running at https://127.0.0.1:44199/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

4.1 查看节点状态

1
kubectl get nodes -o wide

输出

1
2
3
4
NAME                        STATUS   ROLES           AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION      CONTAINER-RUNTIME
k8s-cluster-control-plane Ready control-plane 27s v1.35.0 172.19.0.4 <none> Debian GNU/Linux 12 (bookworm) 5.15.0-40-generic containerd://2.2.0
k8s-cluster-worker Ready <none> 17s v1.35.0 172.19.0.3 <none> Debian GNU/Linux 12 (bookworm) 5.15.0-40-generic containerd://2.2.0
k8s-cluster-worker2 Ready <none> 17s v1.35.0 172.19.0.2 <none> Debian GNU/Linux 12 (bookworm) 5.15.0-40-generic containerd://2.2.0

字段解释

字段 说明
NAME 节点名称,Kind 以 集群名-角色 命名
STATUS 节点状态,Ready 表示节点正常,可接受调度 Pod
ROLES 节点角色,control-plane 是控制面,<none> 是工作节点
AGE 节点创建后的运行时间
VERSION 节点上的 Kubernetes 版本
INTERNAL-IP 节点在集群内部的 IP 地址(Docker 网络)
EXTERNAL-IP 外部可访问的 IP(Kind 环境无)
OS-IMAGE 节点容器内的操作系统
CONTAINER-RUNTIME 容器运行时(containerd 是 K8s 默认的容器运行时)

4.2 查看系统组件

1
kubectl get pods -A

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NAMESPACE            NAME                                                READY   STATUS    RESTARTS   AGE
kube-system coredns-7d764666f9-9xzlc 1/1 Running 0 18s
kube-system coredns-7d764666f9-s9pfm 1/1 Running 0 18s
kube-system etcd-k8s-cluster-control-plane 1/1 Running 0 25s
kube-system kindnet-267dt 1/1 Running 0 19s
kube-system kindnet-kxncz 1/1 Running 0 18s
kube-system kindnet-rp49k 1/1 Running 0 18s
kube-system kube-apiserver-k8s-cluster-control-plane 1/1 Running 0 25s
kube-system kube-controller-manager-k8s-cluster-control-plane 1/1 Running 0 25s
kube-system kube-proxy-4bbcx 1/1 Running 0 18s
kube-system kube-proxy-5mq89 1/1 Running 0 19s
kube-system kube-proxy-6bzmj 1/1 Running 0 18s
kube-system kube-scheduler-k8s-cluster-control-plane 1/1 Running 0 25s
local-path-storage local-path-provisioner-67b8995b4b-fpggw 1/1 Running 0 18s

参数解释

  • -A--all-namespaces:显示所有命名空间的 Pod

字段解释

字段 说明
NAMESPACE 命名空间,用于资源隔离。kube-system 存放系统组件
NAME Pod 名称(格式:部署名-随机ID
READY 就绪状态,格式 就绪容器数/总容器数
STATUS Pod 状态,Running 表示正常运行
RESTARTS 容器重启次数
AGE Pod 创建后的运行时间

系统组件说明

Pod 功能
coredns 集群内 DNS 服务,让 Pod 可以通过 Service 名称访问服务(如 nginx-svc.default.svc.cluster.local
etcd K8s 的数据库,存储集群所有状态信息(键值存储)
kindnet Kind 的 CNI 网络,负责 Pod 网络通信
kube-apiserver K8s API 服务器,所有请求(kubectl、应用)都通过它
kube-controller-manager 控制器管理器,维护集群状态(如 Pod 数量、节点健康)
kube-scheduler 调度器,决定新 Pod 运行在哪个节点上
kube-proxy 代理组件,负责 Service 的网络转发规则
local-path-provisioner 存储供应器,按需创建本地存储卷

命名空间(Namespace) 是类似文件系统的目录,用于隔离资源:

  • 不同命名空间的资源互不可见(除非跨命名空间访问)
  • kube-system:系统组件的命名空间
  • default:用户默认的命名空间

5. 部署测试应用

5.1 创建 Deployment

Deployment是 K8s 中管理应用的资源类型,

  • 声明式定义:描述期望状态(如 nginx 运行 3 个副本)
  • 无状态应用控制器:自动维持 Pod 的数量和状态,挂了自动重启,更新自动滚动。Pod 是 k8s 中的最小运行单位(一个容器组)
  • Deployment 并不直接操作 Pod,它通过管理 ReplicaSet(副本集)来实现副本管理(Replicas)、滚动更新(Rolling Update)、版本回滚(Rollback)等功能
1
2
kubectl create deployment nginx --image=nginx:alpine --replicas=3
kubectl create deployment httpbin --image=kennethreitz/httpbin --replicas=2
  • create deployment:创建 Deployment(应用部署声明)
  • --image:使用的容器镜像
  • --replicas:副本数量,即运行多少个 Pod

查看当前的 deployment:

1
2
3
4
# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
httpbin 2/2 2 2 122m
nginx 3/3 3 3 122m
  • READY:就绪副本数/期望副本数
  • UP-TO-DATE:已更新到最新版本的副本数
  • AVAILABLE:可用的副本数

查看当前的业务 pods

1
2
3
4
5
6
7
8
# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
httpbin-75c95868c8-b429q 1/1 Running 0 3h49m app=httpbin,pod-template-hash=75c95868c8
httpbin-75c95868c8-xh99r 1/1 Running 0 3h49m app=httpbin,pod-template-hash=75c95868c8
nginx-6658f59756-h82mq 1/1 Running 0 3h49m app=nginx,pod-template-hash=6658f59756
nginx-6658f59756-h8k5j 1/1 Running 0 3h49m app=nginx,pod-template-hash=6658f59756
nginx-6658f59756-z6zgf 1/1 Running 0 3h49m app=nginx,pod-template-hash=6658f59756

  • httpbin 的 2 个 Pod 也分布在两个 worker 节点
  • nginx 的 3 个 Pod 分布在 worker 和 worker2 两个节点
  • 这是 K8s 调度器自动决定的,会考虑负载均衡

5.2 创建 Service

kubectl expose 的作用是为一组 Pod(通常是 Deployment 管理的 Pod)创建一个 Service,从而让这些 Pod 能够被访问。在 K8s 中,Pod 的 IP 地址是动态且不可靠的。如果一个 Pod 退出了,Deployment 会拉起一个新的 Pod,但新 Pod 的 IP 会变,这样就会导致客户端(或其它服务)无法通过一个固定的 IP 找到你的应用kubectl expose 会创建一个 Service。Service 拥有一个固定的虚拟 IP(ClusterIP),它像一个负载均衡器,会自动追踪并转发流量给后端那些变化的 Pod。

1
2
kubectl expose deployment nginx --port=80 --target-port=80 --type=NodePort --name=nginx-svc
kubectl expose deployment httpbin --port=80 --target-port=80 --type=NodePort --name=httpbin-svc
  • expose deployment:为 Deployment 创建 Service
  • --port:Service 对外暴露的端口,其他服务访问 Service 时使用该端口
  • --target-port:容器内部的应用端口,也就是你的程序在容器里实际监听的端口
  • --type=NodePort:type 指定了 service 类型
    • ClusterIP (默认): 仅在集群内部可见,适合服务间通信
    • NodePort: 在每个 Node 上开启一个高位端口(30000+),让你可以通过 节点IP:端口 从集群外部访问
    • LoadBalancer: 如果你在云环境(阿里云、AWS),它会自动去申请一个公网负载均衡器

查看当前 service:

1
2
3
4
5
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpbin-svc NodePort 10.108.21.16 <none> 80:31995/TCP 135m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 135m
nginx-svc NodePort 10.97.67.102 <none> 80:30858/TCP 135m
  • NAME:服务名称,集群内其他 Pod 可以通过这个名字直接访问(依靠 CoreDNS)
  • TYPE:服务类型,
    • ClusterIP: 仅限集群内访问
    • NodePort: 在每个节点上开启一个固定端口,允许外部访问
  • CLUSTER-IP:集群内部虚拟 IP,K8s 为服务分配的固定 IP。即使后端的 Pod 销毁重建,这个 IP 也永远不变
  • EXTERNAL-IP:外部访问 IP,在 Kind 或私有环境通常为 <none>。只有在阿里云/AWS 等云环境使用 LoadBalancer 时才会显示 IP
  • PORT(S):端口映射,格式为 Service端口:NodePort端口/协议。这是流量进入的 大门

需要注意,当你创建一个 NodePort 或 LoadBalancer 类型的 Service 时,Kubernetes 仍然会自动为其分配一个 ClusterIP(除非你显式设置 clusterIP: None,即 Headless Services。在 Kubernetes 的设计逻辑中,Service 类型是**层层递进(继承)**的。你可以把这看作一个 包含关系

  • ClusterIP:最基础的,只有 ClusterIP
  • NodePort:包含 ClusterIP + NodePort
  • LoadBalancer:包含 ClusterIP + NodePort + External LoadBalancer IP(云厂商提供)

这样实现的原因是:

  • 统一的内部访问入口:集群内的 Pod 可以通过 ClusterIP 稳定访问,无需知道 NodePort
  • kube-proxy 转发逻辑:kube-proxy 依赖 ClusterIP 配置 iptables/IPVS 规则
  • 负载均衡:ClusterIP 提供负载均衡,NodePort 最终也转发到 ClusterIP

当你通过 NodePort 访问服务时,流量经过的链路如下:

  • 用户请求 节点IP:31995
  • 节点网络层捕获流量,根据 iptables 或 IPVS 规则将其转发给 ClusterIP (10.108.21.16:80)。
  • ClusterIP 负载均衡器再将流量转发给具体的 Pod IP

如果没有 ClusterIP,流量在进入节点后就没有一个统一的、稳定的目的地来做负载均衡了

因此对于上述服务:

  • httpbin-svc (NodePort):

    • 内部访问:集群里的其他 Pod 可以访问 10.108.21.16:80 或直接访问 http://httpbin-svc
    • 外部访问:因为它是 NodePort 类型,你可以通过任何一个 K8s 节点的 IP 加上端口 31995 来访问它
  • nginx-svc (NodePort) 也是类似的:

    • 内部访问: 访问 10.97.67.102:80 或直接访问 http://nginx-svc
    • 外部访问:通过任意节点的 IP 加端口 30858
  • kubernetes (ClusterIP)

    • 这是系统的内置服务,类型为 ClusterIP,只能集群内访问
    • 这个服务是给集群内部组件访问 K8s API 的,即让集群里的 Pod 能够找到并调用 K8s API Server

6. 服务访问测试

6.1 通过临时 Pod 测试

由于 Kind 集群节点运行在 Docker 容器内,直接从宿主机访问 NodePort 较复杂。使用临时 Pod 测试是最可靠的方式。

1
kubectl run curl-test --image=curlimages/curl:latest --rm --restart=Never -it -- curl -s http://nginx-svc.default.svc.cluster.local/

输出:

1
2
3
4
5
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
......
  • kubectl run:创建一个临时 Pod 并运行命令
  • --rm:Pod 执行完毕后自动删除
  • --restart=Never:不自动重启
  • -it:交互模式,可以看到输出
  • curl -s:静默模式请求 URL

这里的 nginx-svc.default.svc.cluster.local 格式为:

  • nginx-svc:Service Name,你创建服务时指定的 NAME
  • default:Namespace,服务所属的命名空间。如果不指定,默认就在 default
  • svc:Resource Type,表示这是一个 Service 资源(区别于 pod)
  • Root Domain:Root Domain,cluster.local,K8s 集群默认的内部根域名(通常在安装时配置)

K8s 的 DNS 搜索域(Search Domains)非常智能,它可以让你在不同场景下使用 缩写

  • 同 Namespace 访问:只需要指定 nginx-svc 即可
  • 跨 Namespace 访问:需要加命名空间,nginx-svc.default
  • 最全路径(全限定域名):即当前这种格式

所以这样访问也是可以的:

1
# kubectl run curl-test --image=curlimages/curl:latest --rm --restart=Never -it -- curl -s http://nginx-svc

通过 NodePort 访问

这条命令在 k8s 集群中创建客户端 Pod 并在客户端 Pod 内访问 nginx-svc 服务。这种集群内服务访问的方式本质上是通过 ClusterIP 的来访问的,如果想在 Kind 环境(节点是 Docker 容器)中通过 NodePort 来访问服务,有以下几种方式:

  • 通过 docker exec 进入节点容器,然后 curl localhost:NodePort
1
2
3
4
# docker exec k8s-cluster-worker curl -I http://localhost:30858

HTTP/1.1 200 OK
......
  • 通过节点 IP(Docker 网络 IP)访问
1
2
3
4
5
6
7
8
9
10
11
# 首先需要获取各个节点(容器)的 IP
# kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.addresses[?(@.type=="InternalIP")].address}{"\n"}{end}'
k8s-cluster-control-plane 172.19.0.4
k8s-cluster-worker 172.19.0.3
k8s-cluster-worker2 172.19.0.2

# 通过容器网络访问
# --noroxy 防止使用宿主机的 http 代理配置
# curl --noproxy "*" http://172.19.0.3:30858/ -I
HTTP/1.1 200 OK
Server: nginx/1.29.8
  • 如果配置了 extraPortMappings,可以通过宿主机端口访问,但是由于我们之前配置的 extraPortMappings 中没有映射 30858 端口,因此这里服务无法通过宿主机端口访问

指定 Service 的 NodePort

我们新创建一个 NodePort 类型的 Service,并指定具体的 NodePort,这样就可以演示通过宿主机的 NodePort 来访问服务了。

  • 服务的配置文件 nginx-external-svc.yaml
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: nginx-external
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30000
  • 应用配置文件:
1
kubectl apply -f /root/code/private/go/test/k8s/nginx-external-svc.yaml
  • 查看服务
1
2
3
# kubectl get svc nginx-external
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-external NodePort 10.96.2.159 <none> 80:30000/TCP 2m5s
  • 通过宿主机映射的端口 30000(映射到容器的 30000 端口,也就是所指定的 NodePort)访问:
1
2
# curl localhost:30000 -I
HTTP/1.1 200 OK
1
2
3
$ curl 10.9.33.133:30000 -I
HTTP/1.1 200 OK
Server: nginx/1.29.8

httpbin 服务也可以通过上面类似的方式访问,这里就不再赘述。

小结

这篇文章详细介绍了如何通过 KIND 在本地 Linux 服务器上搭建 k8s 环境,并学习了 k8s 的 集群、节点、Pod、Deployment、Service 等基础知识,后续我们就可以继续在这个环境中学习和实验 k8s 的各种功能特性了。