前置:建立 K3s 练习环境
什么时候需要集群
前面的课程已经完成单机 Compose 发布。只有需要学习调度、声明式应用管理或贴近生产 Kubernetes 环境时,才进入本课;单机 Compose 仍可作为小项目的合理选择。
本课使用独立的 Ubuntu 24.04 LTS 实验机,不在已有业务服务器直接安装。K3s server 的官方基础要求为 2 核 CPU、2 GB 内存,业务和高级课程的 Argo CD 还需额外容量。单节点适合练习,但不具备节点故障容错能力;轻量发行版也不消除网络、存储和权限知识的学习成本。
安装与版本记录
准备 sudo 权限、可用 DNS、足够磁盘和镜像仓库出口。按实际访问路径设置防火墙:API 6443 仅对管理来源开放,HTTP 80 仅在入口练习需要时开放,不把集群端口全部开放到公网。
curl -fL https://get.k3s.io -o /tmp/install-k3s.sh
less /tmp/install-k3s.sh
# 检查脚本后安装 stable 通道;团队实验应另行固定审核过的 INSTALL_K3S_VERSION。
sudo env INSTALL_K3S_CHANNEL=stable sh /tmp/install-k3s.sh
sudo k3s --version
sudo systemctl status k3s --no-pager
sudo k3s kubectl get nodes
sudo k3s kubectl get pods -A记录安装日期与 k3s --version。使用 stable 通道会随时间变化,不能把一次实验结果视为所有版本都兼容。后续命令使用 kubectl,首次设置当前练习用户的客户端:
mkdir -p "$HOME/.kube"
sudo install -m 600 -o "$(id -u)" -g "$(id -g)" /etc/rancher/k3s/k3s.yaml "$HOME/.kube/config"
export KUBECONFIG="$HOME/.kube/config"
kubectl get nodes上述配置具有集群管理权限,只用于当前实验机,不上传 Git、不交给 CI。配置中地址为本机回环地址;远程访问还涉及证书与网络,不能只复制文件就假定可用。
安装成功的证据
节点应为 Ready;长期运行的系统 Pod 应就绪;安装 Job 的 Completed 属于正常终态,持续 Pending 或 ImagePullBackOff 不属于成功。
kubectl get events -A --sort-by=.metadata.creationTimestamp
kubectl -n kube-system get svc
kubectl -n kube-system get pods -o wide
sudo journalctl -u k3s -n 100 --no-pager| 现象 | 下一步 |
|---|---|
Pending | 对具体 Pod 执行 kubectl describe,检查资源、端口和调度事件 |
ImagePullBackOff | 检查完整镜像名、网络和仓库认证 |
NotReady | 查看 kubelet / K3s 日志、磁盘和网络插件状态 |
permission denied | 检查 kubeconfig 所有者,不将配置改为全员可读 |
K3s 的镜像由 d 管理,宿主机 docker login 不会自动给它授权。仓库镜像配置必须采用实际可用的服务地址,不把普通镜像仓库直接当作通用镜像代理。需要写配置时使用 sudo tee /etc/rancher/k3s/registries.yaml,普通 shell 的 sudo cat > 文件 无法给重定向提权。
默认入口与端口冲突
K3s 默认安装 Traefik,其 LoadBalancer 通过 ServiceLB 使用节点的 80/443 端口。因此本系列不再创建第二个占用 80 的 Nginx LoadBalancer,而是在下面使用 ClusterIP Service + Ingress 共享入口。
ServiceLB 并不意味着自动购买云负载均衡器,也不保证分配新的公网 IP。应检查 Service 状态、节点地址与云防火墙后再测试访问。
把镜像放进集群
前置条件:本节前置的 K3s 节点 Ready、Traefik 就绪,且已取得 17 篇 CI 流水线 的 image.txt。本课先用占位镜像生成配置,再用脚本替换为自己的 digest;不要直接 apply 占位值。
在练习仓库创建 k8s/base。后续课程沿用这些文件,并在 21 篇 多环境隔离与制品晋级 通过 Kustomize 组织环境。
Deployment 与 Service
保存 k8s/base/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: delivery-demo
spec:
replicas: 2
revisionHistoryLimit: 3
progressDeadlineSeconds: 120
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: delivery-demo
template:
metadata:
labels:
app: delivery-demo
spec:
containers:
- name: web
image: ghcr.io/replace-me/delivery-demo:replace-me
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /healthz
port: 80
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mireadiness 决定是否接收 Service 流量,liveness 失败可能触发重启。真实应用的 liveness 不宜直接依赖外部数据库,否则数据库波动可能造成重启风暴。滚动更新需要额外一个 Pod 的容量,不能仅配置副本数就承诺零中断。
保存 k8s/base/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: delivery-demo
spec:
selector:
app: delivery-demo
ports:
- port: 80
targetPort: 80
type: ClusterIPService 用 label 找 Pod。selector 拼错时,Service 仍存在,但没有可用后端。
通过 Ingress 访问
保存 k8s/base/ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: delivery-demo
spec:
ingressClassName: traefik
rules:
- host: demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: delivery-demo
port:
number: 80在练习终端执行。临时渲染目录避免覆盖 Git 中的占位模板:
kubectl create namespace demo
mkdir -p /tmp/delivery-manifests
cp k8s/base/*.yaml /tmp/delivery-manifests/
export IMAGE_REF="$(cat image.txt)"
python3 - <<'CODE'
import os, pathlib, re
ref = os.environ['IMAGE_REF']
assert re.fullmatch(r'ghcr.io/[a-z0-9._/-]+@sha256:[a-f0-9]{64}', ref)
p = pathlib.Path('/tmp/delivery-manifests/deployment.yaml')
p.write_text(p.read_text().replace('ghcr.io/replace-me/delivery-demo:replace-me', ref))
CODE
kubectl -n demo apply -f /tmp/delivery-manifests/
kubectl -n demo rollout status deployment/delivery-demo --timeout=120s
curl --fail -H 'Host: demo.local' http://127.0.0.1/healthz
curl --fail -H 'Host: demo.local' http://127.0.0.1/curl 在 K3s 节点运行;外部访问改为可达的节点 IP,并放通对应来源的 80 端口。这里是 HTTP 实验,生产入口需配置域名和 TLS。
私有镜像需要在 demo namespace 预先创建 kubernetes.io/dockerconfigjson 类型的 ghcr-read Secret,然后在 Deployment 的 spec.template.spec 下添加:
imagePullSecrets:
- name: ghcr-read可在临时 DOCKER_CONFIG 目录用只读账号登录,再通过 kubectl -n demo create secret generic ghcr-read --from-file=.dockerconfigjson=配置文件路径 --type=kubernetes.io/dockerconfigjson 导入。勿输出或提交认证文件;后续每个 namespace 都需配置自己的凭据。
更新与回退
用 17 篇构建 v2,保存其引用为 image-v2.txt。记录旧版后更新:
kubectl -n demo get deployment delivery-demo -o jsonpath='{.spec.template.spec.containers[0].image}' > old-image.txt
kubectl -n demo set image deployment/delivery-demo web="$(cat image-v2.txt)"
kubectl -n demo rollout status deployment/delivery-demo --timeout=120s
curl --fail -H 'Host: demo.local' http://127.0.0.1/在实验环境故意指定不存在的标签,观察事件;确认旧副本仍提供服务,再执行:
kubectl -n demo rollout undo deployment/delivery-demo
kubectl -n demo rollout status deployment/delivery-demo --timeout=120srollout undo 恢复 Deployment 的历史 Pod 模板,不恢复数据库、外部配置或文件。进入 GitOps 后,应在 Git 回退期望状态,避免控制器把手工回退再次覆盖。
最小验收与排查
- 两个 Pod Ready,首页显示期望版本,健康端点返回
ok。 - 查看
kubectl -n demo get endpointslices,确认 Service 有后端地址。 - 镜像错误时查看
kubectl -n demo describe pod POD名称;运行错误查看kubectl -n demo logs deployment/delivery-demo。 - 入口 404 检查 Host 和 Ingress class;503 检查 selector 和 readiness。
- 失败版本未就绪时不能宣告发布成功,恢复后再次检查实际镜像。
参考:Deployment、探针、K3s 要求。