为什么需要零成本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
|
注意:.secrets和kubeconfig.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流水线
我们的流水线包含三个核心阶段,和生产环境的流程完全一致:
- 代码检查阶段:运行代码格式检查、单元测试,提前发现语法错误
- 镜像构建阶段:把代码打包成Docker镜像,推送到本地Registry
- 部署阶段:把新的镜像部署到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分钟,你可以看到每个阶段的输出:
- 首先运行lint-test阶段,代码检查通过后进入下一个阶段
- 然后运行build-push阶段,构建镜像并推送到本地Registry
- 最后运行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练习环境,可以尽情尝试各种云原生方案,不用担心产生任何费用。