这篇文章将学习如何使用 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 | docker info --format '{{.ServerVersion}}' |
安装 Kind
1 | curl -sLo /usr/local/sbin/kind "https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64" |
1 | # kind version |
2.2 安装 Kubectl
1 | curl -sLo /usr/local/sbin/kubectl "https://dl.k8s.io/release/v1.35.0/bin/linux/amd64/kubectl" |
1 | # kubectl version |
- kubectl 是 Kubernetes 的命令行工具,kind 本身不需要 kubectl,但是为了方便管理集群和资源,建议安装 kubectl
- kubectl 用于与 K8s API Server 交互,管理集群资源
- Kustomize 是 K8s 原生的配置管理工具,内置于 kubectl
- kubectl 的官方安装文档,可以参考这里
- 这里我们安装的 kubectl 版本和 k8s 版本都是最新的稳定版本
3. 创建集群
3.1 编写集群配置文件
创建文件 kind-config.yaml:
1 | kind: Cluster |
配置详解:
| 配置项 | 说明 |
|---|---|
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 | Creating cluster "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 | # kubectl config view |
- clusters 字段:集群信息,包括证书和 API 服务器地址,通过 kubectl 命令和集群交互时,就是往这个 API 服务器发送请求
- users:用户信息,包括客户端证书和密钥,用于认证和授权
- contexts:当前使用的上下文,即当前连接的集群和用户,实现一个逻辑绑定,将用户和集群关联起来
- current-context:默认的上下文,执行 kubectl 时会自动使用这个上下文
1 | # kubectl cluster-info |
4.1 查看节点状态
1 | kubectl get nodes -o wide |
输出:
1 | NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME |
字段解释:
| 字段 | 说明 |
|---|---|
| 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 | NAMESPACE NAME READY STATUS RESTARTS AGE |
参数解释:
-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 | kubectl create deployment nginx --image=nginx:alpine --replicas=3 |
create deployment:创建 Deployment(应用部署声明)--image:使用的容器镜像--replicas:副本数量,即运行多少个 Pod
查看当前的 deployment:
1 | # kubectl get deployment |
READY:就绪副本数/期望副本数UP-TO-DATE:已更新到最新版本的副本数AVAILABLE:可用的副本数
查看当前的业务 pods
1 | # kubectl get pods --show-labels |
- 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 | kubectl expose deployment nginx --port=80 --target-port=80 --type=NodePort --name=nginx-svc |
expose deployment:为 Deployment 创建 Service--port:Service 对外暴露的端口,其他服务访问 Service 时使用该端口--target-port:容器内部的应用端口,也就是你的程序在容器里实际监听的端口--type=NodePort:type 指定了 service 类型- ClusterIP (默认): 仅在集群内部可见,适合服务间通信
- NodePort: 在每个 Node 上开启一个高位端口(30000+),让你可以通过
节点IP:端口从集群外部访问 - LoadBalancer: 如果你在云环境(阿里云、AWS),它会自动去申请一个公网负载均衡器
查看当前 service:
1 | # kubectl get svc |
- 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 来访问它
- 内部访问:集群里的其他 Pod 可以访问
-
nginx-svc (NodePort) 也是类似的:
- 内部访问: 访问 10.97.67.102:80 或直接访问
http://nginx-svc - 外部访问:通过任意节点的 IP 加端口 30858
- 内部访问: 访问 10.97.67.102:80 或直接访问
-
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 | <!DOCTYPE html> |
kubectl run:创建一个临时 Pod 并运行命令--rm:Pod 执行完毕后自动删除--restart=Never:不自动重启-it:交互模式,可以看到输出curl -s:静默模式请求 URL
这里的 nginx-svc.default.svc.cluster.local 格式为:
nginx-svc:Service Name,你创建服务时指定的 NAMEdefault:Namespace,服务所属的命名空间。如果不指定,默认就在 defaultsvc: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 | # docker exec k8s-cluster-worker curl -I http://localhost:30858 |
- 通过节点 IP(Docker 网络 IP)访问
1 | # 首先需要获取各个节点(容器)的 IP |
- 如果配置了
extraPortMappings,可以通过宿主机端口访问,但是由于我们之前配置的extraPortMappings中没有映射30858端口,因此这里服务无法通过宿主机端口访问
指定 Service 的 NodePort
我们新创建一个 NodePort 类型的 Service,并指定具体的 NodePort,这样就可以演示通过宿主机的 NodePort 来访问服务了。
- 服务的配置文件
nginx-external-svc.yaml:
1 | apiVersion: v1 |
- 应用配置文件:
1 | kubectl apply -f /root/code/private/go/test/k8s/nginx-external-svc.yaml |
- 查看服务
1 | # kubectl get svc nginx-external |
- 通过宿主机映射的端口 30000(映射到容器的 30000 端口,也就是所指定的 NodePort)访问:
1 | # curl localhost:30000 -I |
1 | $ curl 10.9.33.133:30000 -I |
httpbin 服务也可以通过上面类似的方式访问,这里就不再赘述。
小结
这篇文章详细介绍了如何通过 KIND 在本地 Linux 服务器上搭建 k8s 环境,并学习了 k8s 的 集群、节点、Pod、Deployment、Service 等基础知识,后续我们就可以继续在这个环境中学习和实验 k8s 的各种功能特性了。