Post

使用Kind本地部署K8S应用程序

背景

最近在学习Golang,感受到Google的技术异常强大的同时,又听说同是Google家出来的K8S非常强大,什么微服务自动伸缩,扩容,定时任务调度啥的,无所不能,而且能非常方便的管理多台物理服务器,甚至是混合服务器(云+本地)。最近就开始简单学习试用了一下K8S,本文简单记录下心得。

准备工具

本地工具

为了在本地使用K8S,需要一些额外工具,因为K8S似乎不支持单机部署。

主流工具两种:KindMinikube

来自ChatGPT的对比

以下是 KindMinikube 两者的对比,帮助您选择适合学习 Kubernetes 的工具:

比较维度KindMinikube
设计目的用于模拟 Kubernetes 集群,特别是测试 Kubernetes API 和开发目的单机 Kubernetes 集群,适合入门和学习
安装和设置简单,基于 Docker 容器,无需 VM略复杂,需要配置虚拟机或容器化运行环境
资源消耗较低,只需 Docker较高,需要配置 VM 或其他驱动程序
支持的环境Docker (无需其他依赖环境)支持多种驱动:VM、Docker、Podman、Bare Metal
启动时间启动速度快,适合快速创建集群启动速度稍慢,受 VM 驱动影响
学习难度偏向高级用户,适合有 Kubernetes 基础的人适合初学者,界面和命令更简单直观
可扩展性可模拟多节点集群,但配置稍复杂单节点集群更简单,也可配置多节点
网络功能集成 Docker 网络,网络配置需要手动调整提供内置的网络插件,更适合入门者使用
适用场景测试 Kubernetes 控制平面、开发环境Kubernetes 基础学习、应用开发与测试
社区支持Kubernetes 官方支持,活跃社区拥有丰富文档和插件支持
容器运行时仅支持 Docker 容器运行时支持 Docker、Containerd、CRI-O 等
复杂性偏复杂,适合深入理解 Kubernetes 集群结构更简单,入门友好
操作系统支持跨平台(需 Docker 支持)跨平台,支持更多环境

总结建议

  • Kind:适合已经有一些 Kubernetes 基础的用户,用来模拟复杂场景或学习 Kubernetes API 的运行细节。对资源需求较低,运行更轻量。
  • Minikube:更适合初学者学习 Kubernetes,安装和使用更直观,提供了更多学习工具和内置插件。

虽然GPT推荐Minikube,但是我看中并选择使用Kind,其比较轻量级,简单粗暴就是最好的。

macos使用kind直接安装:

1
brew install kind

练习应用程序

这里使用学生管理系统(完全由ChatGPT生成)来做演示练习,将其用K8S来部署。

架构:

  • 前端:html,css,js文件各一个
  • 后端 :golang
  • 数据库: mongodb

代码部分这里就不贴,源码请查看github

Kind配置

首先,我们要明白Kind的工具的设计理念。

Kind的职责是帮你用yaml配置文件(kind来定义)和定制的docker镜像(kind提供)来启动一个K8S兼容的Cluster,这样你可以直接用kubectl cli工具来直接访问本地的集群(模拟出来的)。

yaml配置

为了创建一个本地k8s cluster, 需要编写yaml配置kind-cluster-config.yaml:

1
2
3
4
5
6
7
8
9
10
11
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraMounts:
      - hostPath: ./frontend  # 宿主机的静态文件路径
        containerPath: /mnt/frontend            # Kind 容器中的挂载路径
    extraPortMappings:
      - containerPort: 80   # 前端服务的端口
        hostPort: 80
        protocol: TCP

注意,这里需要暴露80端口,这样咱们才能通过浏览器访问页面;然后要把前端文件的目录挂载到容器中,以让ingress-nginx来托管我们的文件。

创建cluster

下面开始创建cluster:

1
kind create cluster --config kind-cluster-config.yaml -n student-cluster

你可以看到输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
student-management-system > kind create cluster --config kind-cluster-config.yaml -n student-cluster
Creating cluster "student-cluster" ...
 ✓ Ensuring node image (kindest/node:v1.31.2) 🖼
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-student-cluster"
You can now use your cluster with:

kubectl cluster-info --context kind-student-cluster

Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/

到这里cluster就创建成功了,你可以使用

1
kubectl get nodes

来查看nodes,输出如下

1
2
3
student-management-system > kubectl get nodes  
NAME                            STATUS   ROLES           AGE   VERSION
student-cluster-control-plane   Ready    control-plane   17m   v1.31.2

kind的默认行为是使用一个docker容器来创建cluster,也就是集群中的所有组件,都在这一个容器中运行,身兼多职。这也是为了节省资源。当你使用docker ps的时候,会发现只有一个student-cluster-control-plane容器在运行:

1
2
3
student-management-system > docker ps
CONTAINER ID   IMAGE                  COMMAND                   CREATED          STATUS          PORTS                                           NAMES
f5d7e0656420   kindest/node:v1.31.2   "/usr/local/bin/entr…"   21 minutes ago   Up 21 minutes   0.0.0.0:80->80/tcp, 127.0.0.1:59974->6443/tcp   student-cluster-control-plane

加载应用镜像到cluster

应用程序的后端,需要打包docker镜像。在真实的k8s环境中,一般都是到一个中央docker仓库中去拉取镜像(一般通过CI环境构建镜像并push镜像到docker仓库)。但是现在我们的cluster是一个docker容器,简单起见,我们直接在宿主机构建镜像然后加载到cluster容器中。

在工程的根目录运行:

1
docker build -t student-backend:latest .

然后加载镜像到cluster

1
kind load docker-image student-backend:latest  -n student-cluster

看到如下输出:

1
2
student-management-system > kind load docker-image student-backend:latest  -n student-cluster 
Image: "student-backend:latest" with ID "sha256:52061ec8b071f8552cc7f186b65a22280ee0aa20d139fc7f238429b569fd0f0d" not yet present on node "student-cluster-control-plane", loading...

K8S配置

前端

静态文件

前端的pod还是需要一个nginx来专门托管前端静态文件。

注意这里的挂载,目的就是把cluster的目录挂载到pod中,以让nignx使用。

k8s/frontend.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - name: frontend
          image: nginx:latest
          ports:
            - containerPort: 80
          volumeMounts:
            - name: frontend-html
              mountPath: /usr/share/nginx/html
      volumes:
        - name: frontend-html
          hostPath:
            path: /mnt/frontend  # Kind 控制平面容器内的路径
            type: Directory

---
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  ports:
    - port: 80         # 服务端口
      targetPort: 80   # Pod 中的容器端口
  selector:
    app: frontend
  type: ClusterIP      # 仅暴露在集群内

请求路由

nginx支持请求路由,一般在docker-compose中,我们会使用nginx来做请求路由。

但是k8s是支持请求路由的,目前主要有两个工具:

  • Ingress
  • Gateway API

来自ChatGPT的对比

Gateway API 与 Ingress 的核心区别

特性IngressGateway API
成熟度已成熟,广泛使用,Kubernetes 核心功能相对较新,仍在发展
功能范围专注于 HTTP/HTTPS 路由管理更通用,支持 TCP、UDP、HTTP(S) 等多协议
灵活性灵活性有限,功能主要取决于实现方式提供更多抽象,支持多租户和高级功能
实现方式基于控制器(如 NGINX、Traefik)基于 CRD(自定义资源定义)
适用场景简单到中等复杂的 HTTP 路由场景更复杂的网络场景,多协议/多租户需求
社区支持社区成熟,文档和工具丰富社区支持逐步增强

适用场景对比

Ingress 的适用场景

  • 项目需要简单的 HTTP 路由管理。
  • 项目已经使用了成熟的 Ingress 实现(如 NGINX、Traefik)。
  • 部署在托管 Kubernetes 服务上(如 GKE、EKS),这些服务往往原生支持 Ingress。

Gateway API 的适用场景

  • 项目有更复杂的路由需求,例如多协议(TCP、UDP)。
  • 多租户环境,需要严格的路由隔离。
  • 想尝试新的 Kubernetes 网络栈功能,或未来计划扩展到高级场景。
  • 有 DevOps 团队可以管理 Gateway API 的复杂性。

为了少踩坑,我们优先使用成熟的Ingress工具。

这里,因为我们本地的cluster是使用kind模拟创建的,所以ingress我们需要安装kind官方提供的ingress-nginx

命令:

1
kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/deploy-ingress-nginx.yaml

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
student-management-system >  kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/deploy-ingress-nginx.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created

后端

k8s/backend.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: student-backend:latest
          imagePullPolicy: IfNotPresent # 优先拉取本地镜像!非常重要的配置
          env:
            - name: MONGO_URI
              value: mongodb://mongodb:27017
          ports:
            - containerPort: 8080

---
apiVersion: v1
kind: Service
metadata:
  name: backend
spec:
  ports:
    - port: 8080       # 服务端口
      targetPort: 8080 # Pod 中的容器端口
  selector:
    app: backend
  type: ClusterIP

数据库

这里需要先创建一个持久卷PersistentVolumeClaim,以存储我们的应用数据。

k8s/mongodb.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongodb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      containers:
        - name: mongodb
          image: mongo:latest
          ports:
            - containerPort: 27017
          volumeMounts:
            - name: mongodb-data
              mountPath: /data/db
      volumes:
        - name: mongodb-data
          persistentVolumeClaim:
            claimName: mongodb-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: mongodb
spec:
  ports:
    - port: 27017
      targetPort: 27017
  selector:
    app: mongodb
  type: ClusterIP # 默认类型,分配一个集群内部的IP,只能在集群内部访问。

应用配置文件

准备好4个k8s yaml配置文件后,应用到k8s环境中

1
kubectl apply -f k8s

稍等片刻,所有pod都应启动并运行:

1
2
3
4
5
student-management-system > kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
backend-b959558cf-gkhg5     1/1     Running   0          102m
frontend-55f598bfb9-nhk2r   1/1     Running   0          102m
mongodb-597ddbbb95-kw7df    1/1     Running   0          102m

打开浏览器,访问localhost

即可访问应用程序

image-20241217151539242

总结与延伸

虽然通过kind工具,我们可以在本地方便的模拟k8s环境,但是可以看到,配置还是有点复杂的。

k8s的设计初衷就是为大型商业项目、用户量百万级别以上、微服务部署的项目提供一个标准化的部署工具。

所以,个人认为,对于小型项目,完全没必要用k8s部署。本地和生产环境,完全可以用docker-compose来部署,简单粗暴。

看看现在的很多小公司,为了使用最新的技术,亦或是卷IT从业者,强行把应用程序拆分用k8s部署,这本身就是本末倒置,舍近求远。

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.