Kubernetes CI/CD 零成本搭建指南:Kind + GitHub Actions 本地练习环境全攻略

无需云服务器,从零开始搭建完整的K8s CI/CD练习环境,包含Kind集群部署、GitHub Actions本地运行、全流水线配置和常见问题解决

为什么需要零成本K8s CI/CD练习环境

对于刚接触云原生的开发者来说,学习Kubernetes CI/CD最头疼的问题就是成本:

  • 云厂商K8s服务月费动辄上百元,仅用来练习性价比极低
  • 本地虚拟机搭建多节点集群消耗大量硬件资源,普通笔记本跑起来卡顿
  • 公有云CI/CD流水线有运行次数限制,调试阶段频繁触发容易超出免费额度

本文介绍的方案可以完全解决这些痛点:全程在本地运行,不需要任何云资源,零成本,所有操作和生产环境完全一致。你只需要一台8G内存以上的笔记本,就能完整模拟从代码提交到应用部署到K8s集群的全流程,非常适合学习、测试和验证CI/CD方案。

你将学到

  • 用Kind快速搭建单节点/多节点K8s本地集群
  • 用act工具在本地运行GitHub Actions流水线,不需要提交代码到远程
  • 搭建本地容器镜像Registry,实现镜像构建、推送、拉取全链路闭环
  • 编写完整的CI/CD流水线配置,包含代码检查、镜像构建、集群部署三个核心阶段
  • 常见环境问题的排查思路和解决方法

前置准备:工具安装与验证

所有工具都支持Windows/macOS/Linux,安装过程非常简单,全程不超过10分钟。

1. 基础工具安装

工具 作用 最低版本要求
Git 代码版本管理 2.30+
Docker 容器运行时,Kind和act都依赖它 20.10+
Kind 用Docker容器模拟K8s节点的工具 0.20+
kubectl K8s命令行客户端 1.25+
act 本地运行GitHub Actions的工具 0.2.50+
GitHub账号 用于配置Actions密钥(本地运行不需要 push 到远程)

各系统安装命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# macOS(用Homebrew)
brew install git docker kind kubectl act

# Linux(用Debian/Ubuntu系)
# 先安装Docker:https://docs.docker.com/engine/install/ubuntu/
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash

# Windows(用Winget)
winget install Git.Git Docker.Docker Kubernetes.kubectl nektos.act

2. 工具验证

安装完成后运行以下命令,确认所有工具正常:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 验证Docker
docker run --rm hello-world
# 输出包含 "Hello from Docker!" 即为正常

# 验证Kind
kind version
# 输出 kind v0.20.0 go1.21 linux/amd64 类似内容即为正常

# 验证kubectl
kubectl version --client
# 输出 Client Version: v1.29.0 类似内容即为正常

# 验证act
act --version
# 输出 act version 0.2.50 类似内容即为正常

注意:Windows用户需要确保Docker Desktop开启了WSL2集成,所有命令都在WSL2终端中运行,避免路径和权限问题。


第一步:搭建Kind本地K8s集群

Kind的核心原理是把K8s的每个节点都做成Docker镜像,用Docker容器来模拟K8s节点,启动一个单节点集群只需要30秒。

1. 编写Kind集群配置文件

首先创建工作目录:

1
mkdir -p ~/k8s-cicd-demo && cd ~/k8s-cicd-demo

新建kind-config.yaml配置文件,这里我们配置单节点集群,同时开放端口映射和本地镜像Registry支持:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  # 把主机的30000-32767端口映射到集群,方便访问NodePort类型的服务
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
    listenAddress: "0.0.0.0"
  - containerPort: 30001
    hostPort: 30001
    listenAddress: "0.0.0.0"
  # 挂载本地目录到集群节点,方便调试
  extraMounts:
  - hostPath: ./data
    containerPath: /data
# 配置本地镜像Registry,后面CI构建的镜像会推送到这里
containerdConfigPatches:
- |-
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"]
    endpoint = ["http://registry:5000"]

2. 启动本地镜像Registry

因为我们的流水线完全在本地运行,不需要把镜像推送到公有镜像仓库,所以先启动一个本地Registry容器:

1
2
3
4
5
6
# 启动本地镜像Registry,端口5000
docker run -d --restart=always -p 5000:5000 --name registry registry:2

# 验证Registry正常运行
curl localhost:5000/v2/_catalog
# 输出 {"repositories":[]} 即为正常

3. 创建Kind集群

运行以下命令创建集群,名称为cicd-demo

1
2
3
4
kind create cluster --name cicd-demo --config kind-config.yaml

# 把本地Registry加入到Kind的网络中,让集群节点可以直接访问
docker network connect kind registry

等待30秒左右,集群创建完成后验证:

1
2
3
4
5
6
7
8
# 查看集群节点
kubectl get nodes
# 输出 NAME                     STATUS   ROLES           AGE   VERSION
# cicd-demo-control-plane   Ready    control-plane   45s   v1.27.3

# 查看所有系统Pod,确认都处于Running状态
kubectl get pods -A
# 所有Pod的STATUS都是Running即为正常,如果有Pending的可以等1分钟再看

4. 测试集群可用性

我们先部署一个简单的Nginx服务,验证集群可以正常工作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 部署Nginx
kubectl create deployment nginx --image=nginx:alpine --replicas=2

# 创建NodePort服务暴露端口30000
kubectl expose deployment nginx --type=NodePort --port=80 --target-port=80 --name=nginx-service
kubectl patch service nginx-service -p '{"spec":{"ports":[{"port":80,"nodePort":30000}]}}'

# 访问测试
curl localhost:30000
# 输出Nginx的欢迎页面内容即为正常

# 清理测试资源
kubectl delete deployment nginx
kubectl delete service nginx-service

现在我们已经有了一个可用的K8s集群,接下来配置CI/CD运行环境。


第二步:配置本地GitHub Actions运行环境

act是一个开源工具,可以解析GitHub Actions的workflow配置文件,在本地Docker容器中运行流水线,完全和GitHub远程运行的效果一致,不需要提交代码到远程仓库,调试非常方便。

1. 配置act

首次运行act会提示你选择默认的runner镜像,我们选择medium大小的镜像即可,包含了常用的构建工具:

1
2
act --help
# 首次运行会弹出选择提示,输入2选择medium镜像

也可以手动配置默认镜像:

1
2
3
4
5
mkdir -p ~/.act
cat > ~/.act/conf.yaml << EOF
- name: default
  image: catthehacker/ubuntu:act-latest
EOF

2. 配置流水线密钥

我们的流水线需要用到K8s的kubeconfig凭证来部署应用到集群,首先把Kind集群的kubeconfig导出到项目目录:

1
kind get kubeconfig --name cicd-demo > kubeconfig.yaml

然后创建.secrets文件,存储流水线需要用到的密钥,act会自动读取这个文件的内容作为GitHub Actions的secrets:

1
2
3
# .secrets 文件
KUBECONFIG=$(cat kubeconfig.yaml | base64 -w 0)
REGISTRY_URL=localhost:5000

注意:.secretskubeconfig.yaml文件包含敏感信息,不要提交到Git仓库,一定要加入到.gitignore中。

3. 测试act运行

我们先写一个简单的测试workflow,验证act可以正常运行:

1
mkdir -p .github/workflows

新建.github/workflows/test.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
name: Test Act
on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Print Hello World
        run: echo "Hello from local GitHub Actions!"
      
      - name: Print secrets
        run: |
          echo "Registry URL: ${{ secrets.REGISTRY_URL }}"
          echo "Kubeconfig length: ${#KUBECONFIG}"
        env:
          KUBECONFIG: ${{ secrets.KUBECONFIG }}

运行这个测试流水线:

1
act -j test --secret-file .secrets

如果输出包含Hello from local GitHub Actions!Registry URL: localhost:5000,说明act配置正常,密钥也能正常读取。


第三步:编写完整CI/CD流水线

我们的流水线包含三个核心阶段,和生产环境的流程完全一致:

  1. 代码检查阶段:运行代码格式检查、单元测试,提前发现语法错误
  2. 镜像构建阶段:把代码打包成Docker镜像,推送到本地Registry
  3. 部署阶段:把新的镜像部署到Kind集群,验证服务正常运行

1. 准备示例应用

我们用一个简单的Go Web服务作为示例应用,代码非常简单,只返回当前版本号:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// main.go
package main

import (
	"fmt"
	"net/http"
	"os"
)

func main() {
	version := os.Getenv("APP_VERSION")
	if version == "" {
		version = "v1.0.0"
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello K8s CI/CD! Current version: %s\n", version)
	})

	fmt.Println("Server starting on port 8080...")
	http.ListenAndServe(":8080", nil)
}

编写go.mod

1
2
3
module demo-app

go 1.21

编写Dockerfile,采用多阶段构建,镜像体积只有10M左右:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 构建阶段
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod .
RUN go mod download
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -o demo-app main.go

# 运行阶段
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/demo-app .
EXPOSE 8080
CMD ["./demo-app"]

编写K8s部署清单deployment.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
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-app
  labels:
    app: demo-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demo-app
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      containers:
      - name: demo-app
        image: localhost:5000/demo-app:latest
        ports:
        - containerPort: 8080
        env:
        - name: APP_VERSION
          value: "v1.0.0"
        resources:
          requests:
            cpu: "10m"
            memory: "32Mi"
          limits:
            cpu: "100m"
            memory: "64Mi"
---
apiVersion: v1
kind: Service
metadata:
  name: demo-app-service
spec:
  type: NodePort
  selector:
    app: demo-app
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 30001

2. 编写CI/CD流水线配置

新建.github/workflows/cicd.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
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
name: K8s CI/CD Pipeline
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  REGISTRY: ${{ secrets.REGISTRY_URL }}
  IMAGE_NAME: demo-app

jobs:
  # 阶段1:代码检查和测试
  lint-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.21'
          cache: true

      - name: Run go fmt
        run: |
          fmt_result=$(go fmt ./...)
          if [ -n "$fmt_result" ]; then
            echo "Code formatting issues found:"
            echo "$fmt_result"
            exit 1
          fi

      - name: Run go vet
        run: go vet ./...

      - name: Run unit tests(如果有)
        run: echo "All tests passed"

  # 阶段2:构建并推送镜像
  build-push:
    needs: lint-test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to local registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: dummy
          password: dummy

      - name: Build and push image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # 阶段3:部署到K8s集群
  deploy:
    needs: build-push
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up kubectl
        uses: azure/setup-kubectl@v3
        with:
          version: 'v1.27.3'

      - name: Load kubeconfig
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
          chmod 600 ~/.kube/config
          kubectl cluster-info

      - name: Replace image tag in deployment
        run: |
          sed -i "s|image: localhost:5000/demo-app:latest|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}|g" deployment.yaml
          sed -i "s|value: \"v1.0.0\"|value: \"${{ github.sha }}\"|g" deployment.yaml

      - name: Deploy to K8s
        run: |
          kubectl apply -f deployment.yaml
          kubectl rollout status deployment/demo-app --timeout=60s

      - name: Verify service
        run: |
          # 这里因为act运行在Docker容器中,需要访问主机的端口,我们用host.docker.internal
          apt update && apt install -y curl
          for i in {1..10}; do
            curl -s http://host.docker.internal:30001 && break
            sleep 2
          done

3. 配置.gitignore

新建.gitignore文件,避免敏感信息和临时文件提交到Git:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 敏感信息
.secrets
kubeconfig.yaml
data/

# 构建产物
*.exe
*.tar.gz

# IDE文件
.vscode/
.idea/

第四步:运行流水线并验证

现在我们已经完成了所有配置,第一次运行完整的CI/CD流水线。

1. 初始化Git仓库

1
2
3
git init
git add .
git commit -m "init: first commit"

2. 运行完整流水线

1
act -j deploy --secret-file .secrets

整个流水线运行时间大概3-5分钟,你可以看到每个阶段的输出:

  1. 首先运行lint-test阶段,代码检查通过后进入下一个阶段
  2. 然后运行build-push阶段,构建镜像并推送到本地Registry
  3. 最后运行deploy阶段,把应用部署到Kind集群并验证服务

3. 验证部署结果

流水线运行成功后,在本地访问服务:

1
2
3
curl localhost:30001
# 输出类似:Hello K8s CI/CD! Current version: a1b2c3d4e5f6...
# 后面的版本号就是你本次提交的Git commit hash

查看集群中的Pod状态:

1
2
3
4
5
6
kubectl get pods
# 输出三个demo-app的Pod,都处于Running状态
# NAME                        READY   STATUS    RESTARTS   AGE
# demo-app-7f9d6d789d-2xqzk   1/1     Running   0          2m
# demo-app-7f9d6d789d-5mn2j   1/1     Running   0          2m
# demo-app-7f9d6d789d-7xzv9   1/1     Running   0          2m

查看镜像是否已经推送到本地Registry:

1
2
curl localhost:5000/v2/demo-app/tags/list
# 输出 {"name":"demo-app","tags":["latest","a1b2c3d4e5f6..."]}

4. 测试流水线迭代

我们修改一下main.go的返回内容,模拟代码迭代:

1
2
// 修改main.go中的返回内容
fmt.Fprintf(w, "Hello K8s CI/CD! Current version: %s\nNew feature added!\n", version)

提交代码,再次运行流水线:

1
2
3
git add main.go
git commit -m "feat: add new feature"
act -j deploy --secret-file .secrets

等待流水线完成后再次访问:

1
2
curl localhost:30001
# 输出会包含新添加的"New feature added!"内容,说明新版本已经部署成功

常见问题排查

1. Kind集群创建失败

问题表现:运行kind create cluster后卡住,或者提示节点无法启动 常见原因和解决方法

  • Docker内存不足:打开Docker Desktop设置,把内存分配调整到4G以上,CPU调整到2核以上
  • 镜像拉取失败:国内用户可以配置Kind使用国内镜像源,在kind-config.yaml中添加:
    1
    2
    3
    4
    
    kubeadmConfigPatches:
    - |
      kind: ClusterConfiguration
      imageRepository: registry.aliyuncs.com/google_containers
    
  • 端口冲突:检查30000、30001、5000端口是否被其他进程占用,修改配置文件中的端口即可

2. act运行报错

问题表现:流水线运行到某个步骤失败 常见原因和解决方法

  • 密钥读取失败:检查.secrets文件的格式是否正确,KUBECONFIG的base64编码是否正确,运行cat .secrets | grep KUBECONFIG | cut -d '=' -f2 | base64 -d验证是否是合法的kubeconfig
  • Docker build失败:检查Dockerfile是否正确,本地是否可以正常执行docker build .
  • 无法访问本地Registry:运行docker network inspect kind确认registry容器已经加入到kind网络中,检查Kind的containerd配置是否正确

3. 镜像拉取失败

问题表现:Pod处于ImagePullBackOff状态 常见原因和解决方法

  • 镜像名称或标签错误:检查deployment.yaml中的镜像名称是否和推送到Registry的一致
  • 本地Registry配置错误:运行docker exec -it cicd-demo-control-plane crictl info | grep localhost:5000确认Registry镜像配置已经生效
  • 网络问题:运行docker exec -it cicd-demo-control-plane curl http://registry:5000/v2/_catalog确认集群节点可以访问Registry

4. 服务无法访问

问题表现:curl localhost:30001超时或连接被拒绝 常见原因和解决方法

  • 服务端口配置错误:检查Service的nodePort是否和kind-config.yaml中映射的端口一致
  • Pod未正常启动:运行kubectl logs <pod名称>查看Pod日志,检查应用是否正常启动
  • 防火墙拦截:检查本地防火墙是否开放了30001端口,Windows/macOS用户需要确认Docker Desktop的端口映射规则是否生效

进阶玩法拓展

这个环境除了基础的CI/CD练习,还可以拓展很多生产环境的功能:

1. 多节点集群配置

修改kind-config.yaml,添加worker节点,模拟生产环境多节点部署:

1
2
3
4
5
nodes:
- role: control-plane
  # 原有配置不变
- role: worker
- role: worker

重新创建集群即可得到1个控制节点+2个工作节点的集群。

2. 流水线优化

  • 添加代码安全扫描阶段:集成SonarQube、Trivy等工具,扫描代码漏洞和镜像安全问题
  • 添加缓存配置:把Go依赖、Docker构建层缓存到本地,加快流水线运行速度
  • 添加灰度发布:用Argo Rollouts实现金丝雀发布、蓝绿发布等高级部署策略

3. 集成其他工具

  • 安装Prometheus+Grafana监控集群和应用指标
  • 安装ELK栈收集日志
  • 安装Argo CD实现GitOps持续部署,完全替代流水线中的部署阶段

这个环境的所有操作和生产环境完全兼容,你在这里调试好的流水线配置只需要修改Registry地址和Kubeconfig密钥,就可以直接部署到公有云的K8s集群中使用。

现在你已经拥有了一个完全免费的K8s CI/CD练习环境,可以尽情尝试各种云原生方案,不用担心产生任何费用。

本博客文章采用 CC BY-NC-SA 4.0 许可协议
服务器推荐

腾讯云 · 新用户专属优惠

本博客部署在腾讯云服务器,稳定运行一年多。如果你是新用户或想搭建个人项目,推荐试试腾讯云的优惠活动。

查看优惠详情 →
阅读 1115
上一篇
量化交易入门指南:从因子研究到回测系统的完整工程化路径
下一篇
'数字化转型中的文化架构建设:TOGAF标准中被低估的非技术因素解析'
广告

📚 关注公众号,免费获取技术材料

扫码关注公众号,回复「资料」领取:

  • 📘 企业架构设计模板
  • 📗 数据治理实施指南
  • 📙 工业软件技术白皮书
公众号二维码

长按或扫描二维码