零成本搭建 K8s CI/CD 练习环境:Kind + GitHub Actions 从零到部署

不用花钱买云服务器,用 Kind 在本地跑 K8s 集群,GitHub Actions 做 CI/CD,完整跑通从代码提交到容器部署的全流程。

学 Kubernetes 最头疼的不是概念,是没有一个能随便折腾的练习环境。买云上的托管 K8s,一个月几百块起步,还怕手抖忘了删集群被扣费。用 Minikube 倒是免费,但跑 CI/CD 流水线的时候总差点意思。

这篇文章给你一个零成本方案:Kind 本地起集群,GitHub Actions 跑流水线,从 git push 到 Pod 运行,全链路打通。不花一分钱,不依赖任何外部服务。

整体架构

三个组件,各管一摊:

组件职责成本
KindDocker 里跑 K8s 集群,本地验证用免费
GitHub Actions云端 CI/CD,构建镜像、跑测试、部署验证公共仓库 2000 分钟/月免费
Docker容器运行时,构建镜像的基础免费

流程一句话总结:本地写代码 → git push → GitHub Actions 自动触发 → 构建 Docker 镜像 → 在 Actions 里起一个 Kind 集群 → 部署并验证 → main 分支的镜像推到 GHCR。

环境准备

你需要的东西:一台 4GB 以上内存的电脑、Docker、kubectl、Kind、Git。

装 kubectl:

1
2
3
4
# Linux
curl -LO "https://dl.k8s.io/release/v1.31.0/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
kubectl version --client

装 Kind:

1
2
3
curl -Lo kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64
chmod +x kind && sudo mv kind /usr/local/bin/
kind version

本地起一个集群验证安装没问题:

1
2
3
kind create cluster --name cicd-lab
kubectl cluster-info
kubectl get nodes

看到节点 Ready 就说明环境 OK。先删掉,后面用 Actions 里的:

1
kind delete cluster --name cicd-lab

写应用和 K8s 清单

用一个极简 Python HTTP 服务做演示,不引任何框架,纯标准库:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# src/app.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps({
            "status": "ok",
            "message": "Hello from K8s CI/CD Lab!",
            "version": "1.0.0"
        }).encode())

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8080), Handler)
    print("Server running on port 8080...")
    server.serve_forever()

Dockerfile 能多小就多小:

1
2
3
4
5
FROM python:3.12-slim
WORKDIR /app
COPY src/ .
EXPOSE 8080
CMD ["python", "app.py"]

K8s 的 Deployment 加上健康检查,这是生产环境的习惯:

 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
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cicd-demo
  labels:
    app: cicd-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: cicd-demo
  template:
    metadata:
      labels:
        app: cicd-demo
    spec:
      containers:
      - name: app
        image: ghcr.io/OWNER/cicd-demo:TAG
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /
            port: 8080
          initialDelaySeconds: 3
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 10
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "250m"

Service 用 ClusterIP 就够了,Actions 里用 port-forward 验证:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: cicd-demo-svc
spec:
  selector:
    app: cicd-demo
  ports:
  - port: 80
    targetPort: 8080
  type: ClusterIP

注意 Deployment 里的 OWNERTAG 是占位符,Actions 流水线会在运行时用 sed 替换。

GitHub Actions 流水线

项目结构长这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
k8s-cicd-lab/
├── src/
│   └── app.py
├── Dockerfile
├── k8s/
│   ├── deployment.yaml
│   └── service.yaml
└── .github/
    └── workflows/
        └── ci-cd.yml

流水线分三个阶段,拆开讲:

阶段一:构建和检查。 检出代码,做语法检查,构建镜像,验证镜像大小不超过 200MB。这个阶段跑得快,有问题直接拦住。

阶段二:K8s 部署测试。helm/kind-action 在 Actions runner 里起一个 Kind 集群,把刚才构建的镜像 kind load 进去(注意不是 push 到远端,是直接灌进 Kind),替换 manifest 里的占位符,apply 部署,rollout status 等就绪,port-forwardcurl 做健康检查。如果这步挂了,自动导出 Pod 日志方便排查。

阶段三:推送镜像。 只在 main 分支触发,用 GITHUB_TOKEN 登录 GHCR,推镜像。PR 不推,避免污染仓库。

 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
name: K8s CI/CD Pipeline
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  IMAGE_NAME: ${{ github.repository_owner }}/cicd-demo
  IMAGE_TAG: ${{ github.sha }}

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: 语法检查
        run: python3 -m py_compile src/app.py
      - name: 构建镜像
        run: docker build -t ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} .

  deploy-test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: 启动 Kind
        uses: helm/kind-action@v1.10.0
        with:
          cluster_name: k8s-cicd
      - name: 构建并加载镜像
        run: |
          docker build -t ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} .
          kind load docker-image ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} \
            --name k8s-cicd
      - name: 替换占位符并部署
        run: |
          sed -i "s|OWNER|${{ github.repository_owner }}|g" k8s/deployment.yaml
          sed -i "s|TAG|${{ env.IMAGE_TAG }}|g" k8s/deployment.yaml
          kubectl apply -f k8s/
      - name: 等待就绪
        run: kubectl rollout status deployment/cicd-demo --timeout=120s
      - name: 健康检查
        run: |
          kubectl port-forward svc/cicd-demo-svc 8080:80 &
          sleep 3
          curl -sf http://localhost:8080 | python3 -m json.tool

  push-image:
    needs: deploy-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: 构建并推送
        run: |
          IMAGE=ghcr.io/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
          docker build -t $IMAGE .
          docker push $IMAGE

关键点解释:kind load docker-image 是把本地构建好的镜像直接灌进 Kind 节点的 Docker 里,省去了 push/pull 的环节,CI 里用这招特别快。needs: build 保证阶段串行。permissions 那块必须显式声明 packages: write,不然 GITHUB_TOKEN 没权限推 GHCR。

跑通全流程

把上面的文件全部提交推到 GitHub:

1
2
3
git add -A
git commit -m "init: K8s CI/CD lab"
git push origin main

去仓库的 Actions tab 看流水线跑。正常情况下三个阶段全绿,最后 GHCR 里多了一个镜像。

之后改代码、提交、推送,流水线自动跑。迭代流程就三步:改代码 → push → 看 Actions 结果。本地想快速验证的话:

1
2
3
docker build -t test-app .
docker run -p 8080:8080 test-app
curl http://localhost:8080

不用每次都等 CI,本地先跑通了再推。

踩坑速查

问题原因解决
ImagePullBackOffKind 集群里没有这个镜像kind load docker-image 灌进去
Actions 里 GITHUB_TOKEN 推镜像 403权限不够仓库 Settings → Actions → Workflow permissions 改成 Read and write
YAML 解析报错缩进用了 Tab 或者对齐不对YAML 严格只认空格,用 yamllint 检查一下
Kind 集群创建卡住上次没删干净kind delete cluster --name xxx 然后重试
port-forward 超时Pod 还没 Readykubectl rollout status 等就绪再转发
kubectl 连不上集群kubeconfig 的 context 不对kubectl config use-context kind-集群名

下一步

跑通这个流水线之后,可以往几个方向扩展:

  • Helm 打包:把 K8s 清单改成 Helm Chart,支持 values 覆盖,多环境复用。
  • ArgoCD:在 Kind 里装一个 ArgoCD,把部署从 push 模式改成 GitOps pull 模式。
  • 多环境隔离:dev / staging / prod 用不同 namespace,同一套 manifest 不同 values。
  • 监控和日志:Prometheus + Grafana 看指标,EFK 收日志,Kind 里跑这些也不吃太多资源。

这套方案最大的好处是零心理负担。集群随便建随便删,流水线跑挂了不用心疼钱。先把基础流程跑通,再往上加东西,比直接在生产环境里摸黑强太多了。

广告
广告位预留中 (728x90)