文章目录
- 整体架构
- 环境准备
- 写应用和 K8s 清单
- GitHub Actions 流水线
- 跑通全流程
- 踩坑速查
- 下一步
学 Kubernetes 最头疼的不是概念,是没有一个能随便折腾的练习环境。买云上的托管 K8s,一个月几百块起步,还怕手抖忘了删集群被扣费。用 Minikube 倒是免费,但跑 CI/CD 流水线的时候总差点意思。
这篇文章给你一个零成本方案:Kind 本地起集群,GitHub Actions 跑流水线,从 git push 到 Pod 运行,全链路打通。不花一分钱,不依赖任何外部服务。
整体架构
三个组件,各管一摊:
| 组件 | 职责 | 成本 |
|---|---|---|
| Kind | Docker 里跑 K8s 集群,本地验证用 | 免费 |
| GitHub Actions | 云端 CI/CD,构建镜像、跑测试、部署验证 | 公共仓库 2000 分钟/月免费 |
| Docker | 容器运行时,构建镜像的基础 | 免费 |
流程一句话总结:本地写代码 →git push→ GitHub Actions 自动触发 → 构建 Docker 镜像 → 在 Actions 里起一个 Kind 集群 → 部署并验证 → main 分支的镜像推到 GHCR。
环境准备
你需要的东西:一台 4GB 以上内存的电脑、Docker、kubectl、Kind、Git。
装 kubectl:
# Linuxcurl-LO"https://dl.k8s.io/release/v1.31.0/bin/linux/amd64/kubectl"chmod+x kubectl&&sudomvkubectl /usr/local/bin/ kubectl version--client装 Kind:
curl-Lokind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64chmod+x kind&&sudomvkind /usr/local/bin/ kind version本地起一个集群验证安装没问题:
kind create cluster--namecicd-lab kubectl cluster-info kubectl get nodes看到节点 Ready 就说明环境 OK。先删掉,后面用 Actions 里的:
kind delete cluster--namecicd-lab写应用和 K8s 清单
用一个极简 Python HTTP 服务做演示,不引任何框架,纯标准库:
# src/app.pyfromhttp.serverimportHTTPServer,BaseHTTPRequestHandlerimportjsonclassHandler(BaseHTTPRequestHandler):defdo_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 能多小就多小:
FROM python:3.12-slim WORKDIR /app COPY src/ . EXPOSE 8080 CMD ["python", "app.py"]K8s 的 Deployment 加上健康检查,这是生产环境的习惯:
# k8s/deployment.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:cicd-demolabels:app:cicd-demospec:replicas:2selector:matchLabels:app:cicd-demotemplate:metadata:labels:app:cicd-demospec:containers:-name:appimage:ghcr.io/OWNER/cicd-demo:TAGports:-containerPort:8080readinessProbe:httpGet:path:/port:8080initialDelaySeconds:3periodSeconds:5livenessProbe:httpGet:path:/port:8080initialDelaySeconds:10periodSeconds:10resources:requests:memory:"64Mi"cpu:"100m"limits:memory:"128Mi"cpu:"250m"Service 用 ClusterIP 就够了,Actions 里用 port-forward 验证:
# k8s/service.yamlapiVersion:v1kind:Servicemetadata:name:cicd-demo-svcspec:selector:app:cicd-demoports:-port:80targetPort:8080type:ClusterIP注意 Deployment 里的OWNER和TAG是占位符,Actions 流水线会在运行时用 sed 替换。
GitHub Actions 流水线
项目结构长这样:
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-forward加curl做健康检查。如果这步挂了,自动导出 Pod 日志方便排查。
阶段三:推送镜像。只在 main 分支触发,用GITHUB_TOKEN登录 GHCR,推镜像。PR 不推,避免污染仓库。
name:K8s CI/CD Pipelineon:push:branches:[main]pull_request:branches:[main]env:IMAGE_NAME:${{github.repository_owner}}/cicd-demoIMAGE_TAG:${{github.sha}}jobs:build:runs-on:ubuntu-lateststeps:-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:buildruns-on:ubuntu-lateststeps:-uses:actions/checkout@v4-name:启动 Kinduses:helm/kind-action@v1.10.0with: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.toolpush-image:needs:deploy-testruns-on:ubuntu-latestif:github.ref == 'refs/heads/main'permissions:contents:readpackages:writesteps:-uses:actions/checkout@v4-uses:docker/login-action@v3with:registry:ghcr.iousername:${{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:
gitadd-Agitcommit-m"init: K8s CI/CD lab"gitpush origin main去仓库的 Actions tab 看流水线跑。正常情况下三个阶段全绿,最后 GHCR 里多了一个镜像。
之后改代码、提交、推送,流水线自动跑。迭代流程就三步:改代码 → push → 看 Actions 结果。本地想快速验证的话:
dockerbuild-ttest-app.dockerrun-p8080:8080 test-appcurlhttp://localhost:8080不用每次都等 CI,本地先跑通了再推。
踩坑速查
| 问题 | 原因 | 解决 |
|---|---|---|
ImagePullBackOff | Kind 集群里没有这个镜像 | 用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 还没 Ready | 先kubectl 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 里跑这些也不吃太多资源。
这套方案最大的好处是零心理负担。集群随便建随便删,流水线跑挂了不用心疼钱。先把基础流程跑通,再往上加东西,比直接在生产环境里摸黑强太多了。