本文记录我在 Mac 上从零搭建 Kubernetes 学习环境的过程,适合第一次接触 Kubernetes 的开发者。

完整分篇教程、YAML、Helm Chart、Dockerfile 和截图:

https://github.com/bysbsh/k8s-local-learning-guide

一、为什么在 Mac 上学习 Kubernetes

入门 Kubernetes 不必先购买服务器,也不必一开始安装 Rancher。使用 Docker Desktop + Kind,可以在 Mac 上创建一个真实的单节点 Kubernetes 集群,成本低、创建快、容易删除和重建。

本地环境的关系如下:

Mac
└── Docker Desktop
    └── Kind 节点容器
        └── Kubernetes 集群
            ├── Deployment / Pod / Service
            ├── Ingress / Traefik
            ├── Argo CD
            └── PVC / PV

本文最终完成这条发布链路:

网页代码 → Docker 镜像 → Kind 节点 → Deployment → Service
                                      ↓
                             Traefik Ingress
                                      ↓
                                Argo CD + Git

把它展开就是一条完整的本地发布链路:

应用代码

Docker 构建镜像

Kind 节点加载镜像

Deployment

Pod 副本

Service

Ingress + Traefik

浏览器访问

Git 仓库

Argo CD

左侧是应用交付链路,右侧是 GitOps 管理链路:应用由 Deployment 运行,Argo CD 负责让集群配置跟 Git 保持一致。

这是学习环境,不是生产环境。Kind 节点运行在 Docker Desktop 中,Docker 或 Mac 停止后集群也会停止。

二、安装本地 Kubernetes

1. 安装 Homebrew 和 Docker Desktop

如果没有 Homebrew,执行:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Apple Silicon Mac 通常需要加载 Homebrew:

echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

安装并启动 Docker Desktop:

brew install --cask docker
open -a Docker
docker version

输出中同时有 ClientServer,说明 Docker 已经准备好。

2. 安装 kubectl、Kind 和 Helm

brew install kubectl kind helm
kubectl version --client
kind version
helm version

三个工具的作用:

工具 作用
kubectl 操作 Kubernetes 资源
kind 在 Docker 容器中创建 Kubernetes
helm 安装和管理 Kubernetes 应用包

3. 创建集群

kind create cluster --name study
kubectl config current-context
kubectl get nodes

如果提示名为 study 的集群已经存在,说明这一步之前已经完成,直接继续执行后面的命令即可。

当前上下文应为 kind-study。节点刚创建时可能是 NotReady,等待几十秒后再次检查,直到出现:

study-control-plane   Ready

下面截图是整套学习环境完成后的最终状态,不代表刚创建集群时已经存在这些 Deployment。
在这里插入图片描述

三、Deployment、Pod 和 Service

1. 创建命名空间和 Deployment

kubectl create namespace k8s-study
kubectl config set-context --current --namespace=k8s-study
kubectl create deployment web --image=nginx:1.29.8-alpine
kubectl get deployments,pods

如果命名空间已经存在,kubectl create namespace 会提示 already exists,这不是故障,可以继续。也可以改用声明式写法:

kubectl create namespace k8s-study --dry-run=client -o yaml | kubectl apply -f -

对象关系是:

Deployment → ReplicaSet → Pod → Container

Deployment 不直接接收请求,它通过 ReplicaSet 创建和维持 Pod,Service 再通过标签找到这些 Pod:

Deployment: web

ReplicaSet

Pod: web-xxx-aaa

Pod: web-xxx-bbb

Pod: web-xxx-ccc

Service: web-service

客户端

Pod 名称和 Pod IP 都可能变化;稳定的访问入口应使用 Service 名称。

Pod 名称类似 web-7fbc579fd4-t8fbvnginx 是镜像或容器名,web 才是 Deployment 名。Pod 是可替换实例,名称和 IP 都可能变化。

2. 扩容和创建 Service

kubectl scale deployment web --replicas=3
kubectl get pods
kubectl expose deployment web \
  --name=web-service \
  --port=80 \
  --target-port=80
kubectl get service web-service

Service 默认是 ClusterIP,提供集群内部的稳定虚拟 IP。它通过标签选择器找到 Pod,而不是依赖固定 Pod 名。

从 Mac 临时访问:

kubectl port-forward service/web-service 8080:80

浏览器打开 http://localhost:8080。按 Ctrl+C 只会停止端口转发,不会删除 Deployment、Service 或 Pod。

集群内部测试:

kubectl run test-client \
  --rm -it \
  --restart=Never \
  --image=curlimages/curl:8.12.1 \
  --command -- curl -I http://web-service

看到 HTTP/1.1 200 OK,说明 Service 已成功转发到 Nginx Pod。

四、YAML、ConfigMap 和 Secret

命令式创建适合快速实验,长期维护应使用 YAML。YAML 可以进入 Git、审查、回滚,也能交给 Argo CD 自动同步。

Deployment 的最小结构:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: k8s-study
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.29.8-alpine
          ports:
            - containerPort: 80

selector.matchLabels 必须和 template.metadata.labels 对应。应用前先检查:

mkdir -p ~/k8s-study
cd ~/k8s-study
nano web.yaml

把上面的 Deployment YAML 粘贴进去。nano 中按 Control+O、回车保存,再按 Control+X 退出。然后执行:

kubectl apply --dry-run=client -f web.yaml
kubectl apply -f web.yaml

如果前面已经用 kubectl create deployment web 创建过同名 Deployment,apply 会把它转换为 YAML 管理并更新配置,这是正常现象。Service 也可以保存为 YAML;本教程前面创建的 web-service 可以继续使用,查看方式如下:

kubectl get service web-service -n k8s-study

ConfigMap 用来保存普通配置或网页内容;Secret 用来保存密码、Token 等敏感配置。Secret 默认是 Base64 编码,不等于自动加密,真实凭据不要提交到 GitHub。

五、Helm、Traefik 和 Ingress

1. Helm 是什么

Helm 是 Kubernetes 的包管理器:

Chart   → 可参数化的 YAML 模板
Release → Chart 在集群中的一次安装实例
Values  → 安装时使用的配置参数

2. 用 Helm 安装 Traefik

helm repo add traefik https://traefik.github.io/charts
helm repo update
helm upgrade --install traefik traefik/traefik \
  --namespace traefik \
  --create-namespace \
  --set service.spec.type=ClusterIP
kubectl get pods,services -n traefik

Ingress 只是路由规则,Traefik 是真正执行规则的 Ingress Controller:

浏览器 → Traefik Controller → Ingress 规则 → Service → Pod

一次 HTTP 请求的实际转发路径如下:

浏览器
web.localhost:8090

Traefik Service

Ingress 规则
host: web.localhost

Service
web-service:80

Pod 1

Pod 2

Ingress 是规则,Traefik 是执行规则的 Controller,Service 才负责把请求分发到具体 Pod。

Traefik 和 ingress-nginx 都是 Controller,学习环境选择一个即可。

3. 创建 Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: k8s-study
spec:
  ingressClassName: traefik
  rules:
    - host: web.localhost
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-service
                port:
                  number: 80
cd ~/k8s-study
nano ingress.yaml

粘贴上面的 Ingress YAML,按 Control+O、回车保存,再按 Control+X 退出。然后执行:

kubectl apply -f ingress.yaml
kubectl port-forward service/traefik -n traefik 8090:80

打开 http://web.localhost:8090.localhost 通常会解析到 127.0.0.1,不需要修改 /etc/hosts

六、Argo CD 与 GitOps

1. GitOps 的核心思想

修改 YAML → git commit → git push → Argo CD 读取 Git → 同步 Kubernetes

Git 是期望状态的来源,Argo CD 负责让集群实际状态追上 Git。

Argo CD 会持续执行下面的比较和同步循环:

存在差异

一致

Git 仓库
期望状态

Argo CD Application

比较 Git 与集群

同步 Kubernetes

保持 Synced

Deployment / Service / ConfigMap

selfHeal 会把手动改动恢复为 Git 中的状态;prune 决定 Git 删除资源后是否也删除集群中的资源。

2. 安装和访问 Argo CD

kubectl create namespace argocd
kubectl apply --server-side --force-conflicts \
  -n argocd \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl get pods -n argocd

访问 Argo CD:

kubectl port-forward service/argocd-server -n argocd 8082:443

打开 https://localhost:8082。本地自签名证书引起的安全提示是正常的。读取初始密码:

kubectl get secret argocd-initial-admin-secret \
  -n argocd \
  -o jsonpath='{.data.password}' | base64 -d; echo

用户名固定为 admin,密码不要写入文章、截图或 Git。

3. Application 和同步状态

本文示例使用公开仓库的 examples/01-basic-web 目录。创建 Application 前,先确保已经安装 Traefik,并准备 Namespace 和演示 Secret:

kubectl apply -f https://raw.githubusercontent.com/bysbsh/k8s-local-learning-guide/main/examples/00-namespace/namespace.yaml
kubectl create secret generic web-credentials \
  -n k8s-study \
  --from-literal=username=demo \
  --from-literal=password='replace-me'

web-credentials 只用于演示 Secret 挂载,不能替换成真实生产密码。Secret 不在公开仓库中,避免把凭据提交到 Git。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: k8s-study
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/bysbsh/k8s-local-learning-guide.git
    targetRevision: main
    path: examples/01-basic-web
  destination:
    server: https://kubernetes.default.svc
    namespace: k8s-study
  syncPolicy:
    automated:
      prune: false
      selfHeal: true
kubectl apply -f application.yaml
kubectl get application k8s-study -n argocd
状态 含义
Synced Git 和集群状态一致
OutOfSync Git 和集群有差异
Progressing 资源正在启动或健康状态尚未汇总
Healthy 资源达到健康判断标准

公开仓库适合只读学习。如果要练习“修改文件、推送 Git、Argo CD 自动部署”,请先 Fork 这个仓库,或新建自己的 Git 仓库,并把 Application 里的 repoURL 改成自己的地址。不要把凭据或未经授权的修改推送到别人的仓库。

实际同步流程如下(在你自己的 GitOps 仓库目录中执行):

cd ~/自己的-gitops-仓库
nano configmap.yaml
git diff
git add configmap.yaml
git commit -m "Update web content"
git push
kubectl annotate application k8s-study \
  -n argocd \
  argocd.argoproj.io/refresh=hard \
  --overwrite
kubectl get application k8s-study -n argocd --watch

看到 SYNC STATUS 变为 Synced,并且资源健康状态恢复,说明 Git 中的新版本已经同步到集群。

在这里插入图片描述

同步较慢时可以主动刷新:

kubectl annotate application k8s-study \
  -n argocd \
  argocd.argoproj.io/refresh=hard \
  --overwrite

4. Argo CD 为什么关掉终端还在运行

Argo CD Pod 运行在 Kind 集群中,不依赖当前终端。port-forward 只是 Mac 的临时访问入口:

Kubernetes → argocd-server Service → kubectl port-forward → localhost:8082

如果希望关闭终端后入口仍然存在:

nohup kubectl port-forward \
  -n argocd \
  service/argocd-server 8082:443 \
  > /tmp/argocd-port-forward.log 2>&1 &

停止后台进程:

lsof -nP -iTCP:8082 -sTCP:LISTEN
kill PID

生产环境通常使用正式域名、HTTPS、Ingress 或 LoadBalancer,不依赖 port-forward

七、PVC、PV 和持久化存储

容器和 Pod 都可能被重建,数据不能只写在容器临时文件系统中:

Pod → PVC(存储申请) → PV(实际存储) → StorageClass(动态供给规则)

PVC 示例:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: storage-demo
  namespace: k8s-study
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi
kubectl apply -f pvc.yaml
kubectl get pvc storage-demo -n k8s-study
kubectl get pv

使用 WaitForFirstConsumer 的 StorageClass 在没有 Pod 引用时显示 Pending 是正常的;Pod 引用后通常会变成 Bound

PVC 不是某个 Pod 的目录,而是一份存储申请。实际绑定关系如下:

Pod

Volume 配置

PVC
storage-demo

StorageClass
standard

PV
pvc-xxxx

节点上的实际数据目录

同一命名空间中的其他 Pod 只有在自己的 YAML 中明确引用同一个 PVC,才可能挂载这份存储。

Namespace 下的所有 Pod 不会自动共享 PVC,必须在 Pod YAML 中明确声明:

spec:
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: storage-demo
  containers:
    - name: web
      volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html

八、自建镜像和滚动发布

Dockerfile 示例:

FROM nginx:1.29.8-alpine
COPY index.html /usr/share/nginx/html/index.html

构建并加载到 Kind:

docker build -t study-web:v1 .
kind load docker-image study-web:v1 --name study
docker exec study-control-plane crictl images | grep study-web

Kind 节点是独立容器,Mac 本地镜像不会自动出现在节点中。使用本地镜像时:

image: study-web:v1
imagePullPolicy: Never

发布 v2:

docker build -t study-web:v2 .
kind load docker-image study-web:v2 --name study

更新 Deployment 中的镜像版本后,观察滚动更新:

kubectl get pods -n k8s-study -l app=my-app --watch
kubectl rollout status deployment/my-app -n k8s-study
kubectl rollout history deployment/my-app -n k8s-study
kubectl rollout undo deployment/my-app -n k8s-study

Deployment 会逐步创建新 Pod,等待新 Pod 就绪后再删除旧 Pod。

发布 v1 到 v2 时,变化的是镜像版本,Deployment、Service 和 Ingress 的职责不变:

修改 index.html

docker build study-web:v2

kind load docker-image

Git 提交 my-app.yaml

Argo CD 同步

Deployment 创建 v2 Pod

就绪后删除 v1 Pod

Service 持续提供访问

九、常见故障排查

遇到问题时按固定顺序检查:

kubectl get pods -n k8s-study -o wide
kubectl describe pod POD_NAME -n k8s-study
kubectl logs POD_NAME -n k8s-study
kubectl get events -n k8s-study --sort-by=.lastTimestamp
kubectl rollout status deployment/DEPLOYMENT_NAME -n k8s-study
错误 常见原因 处理方向
apiVersion not set YAML 顶层字段缺失或缩进错误 检查 apiVersionkindmetadata
ImagePullBackOff 镜像不存在、网络失败或 Kind 没有本地镜像 检查 Events,必要时 kind load docker-image
mountPath must be unique 同一容器重复挂载相同目录 删除重复 volumeMounts
PVC Pending 没有 Pod 引用或 StorageClass 尚未供给 查看 PVC、Pod 和 Events
Argo CD OutOfSync Git 与集群存在差异 检查 revision,执行 refresh
zsh no matches found 方括号被 zsh 当成通配符 给参数加单引号

zsh 中查看容器镜像时,给 custom-columns 加单引号:

kubectl get pods -n k8s-study \
  -o 'custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[0].image'

Argo CD 页面使用 https://localhost:8082。如果入口已退出,重新执行:

kubectl port-forward service/argocd-server -n argocd 8082:443

十、清理学习环境

停止 port-forward:在对应终端按 Ctrl+C。删除整个 Kind 学习集群:

kind delete cluster --name study

这会删除 Kind 节点、Kubernetes 资源、Argo CD、Helm Release、PVC/PV 和加载到 Kind 节点的镜像副本,但不会删除 Mac 上安装的软件或 Git 仓库。

十一、总结

  1. Docker Desktop + Kind 创建本地集群。
  2. Deployment 管理 Pod 副本。
  3. Service 提供稳定的集群内部入口。
  4. ConfigMap 和 Secret 管理配置。
  5. Helm 安装 Traefik,Ingress 将域名路由到 Service。
  6. PVC/PV 保存 Pod 重建后的数据。
  7. Dockerfile 构建镜像并完成 v1 到 v2 的滚动发布。
  8. Argo CD 根据 Git 自动同步 Kubernetes。
Pod 会变,Service 提供稳定入口。
Ingress 只描述路由,Controller 才真正转发流量。
Helm 管理应用包,Argo CD 管理 Git 与集群的一致性。
容器会重建,PVC 保存持久数据。
Kind 适合学习,生产环境还需要正式入口、HTTPS、备份、监控和高可用。

完整教程和源码

本文对应的完整分篇教程、示例 YAML、Helm Chart、Dockerfile、截图和故障排查手册:

https://github.com/bysbsh/k8s-local-learning-guide

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐