Kubernetes Job 与 Service 实战笔记(Job · CronJob · Service · NodePort)

本手册由 master30_2026-08-10_14_17_43.log(8/10 全天实验)整理而成,在 kubeadm v1.30.2 集群(master30 + worker31/32,运行第 5 天)上完成。
两大主题:① 批处理(Job / CronJob,含失败重试、并行、超时、TTL)→ ② 服务发现(hostPort / ClusterIP / selector / NodePort)。命令提示符保留真实时间戳,报错与修复全部来自实际操作。

📑 目录

# 章节 核心内容
整体框架与实验总览 实验流程图、两大主题、节点环境
实验准备:命名空间与上下文 controller / services 命名空间、set-context 切换
Job 基础:一次性任务 命令行创建、job.yaml、查看日志、同名冲突
Job 失败重试:restartPolicy 与 backoffLimit 故意写错命令、两种重启策略、最多重建次数
Job 高级参数 completions / parallelism / activeDeadlineSeconds / TTL
CronJob 定时任务 每 2 分钟执行、自动生成 Job、历史保留
hostPort 的局限 Pod IP 直连、宿主机端口、端口冲突
ClusterIP Service:负载均衡 创建 Service、Endpoints、60 次轮询验证
Service 进阶:selector 与 expose 相同标签自动接入、错误 selector、rollout restart
实战:MySQL + WordPress NodePort expose mysql、ClientIP 连接、NodePort 对外访问
十一 常见错误与排查(重点) 本次实验全部报错:原因 + 修复
十二 命令速查表 Job / CronJob / Service 常用命令

一、整体框架与实验总览

1.1 实验流程图

集群已运行第 5 天
master30 + worker31/32

批处理主题
Job → 失败重试 → 高级参数 → CronJob

服务发现主题
hostPort 局限 → ClusterIP 负载均衡 → selector

实战
MySQL + WordPress NodePort

错误复盘 + 命令速查

1.2 节点环境

节点 主机名 IP 角色
master30 master30.ningcode.cn 10.1.8.30 控制平面
worker31 worker31.ningcode.cn 10.1.8.31 工作节点
worker32 worker32.ningcode.cn 10.1.8.32 工作节点
  • Pod 网段 10.224.0.0/16;Service 网段 10.96.0.0/12(本次 Service IP 如 10.100.254.252、10.108.123.75)
  • 私有镜像仓库 hub.laoma.cloud(实验期间 192.168.46.201:80 曾连接拒绝,遇到拉镜像失败改用官方镜像或 nerdctl 预热)

1.3 主题速览

主题 核心知识点 日志时间
Job 一次性任务、失败重试、completions/parallelism/超时/TTL 14:17–15:28
CronJob 定时创建 Job、历史保留 3 个 15:28–15:32
Service hostPort 局限、ClusterIP 负载均衡、selector 16:20–16:40
实战 MySQL expose + wordpress NodePort(31055) 17:30–17:35

↑ 回到目录


二、实验准备:命名空间与上下文

2.1 检查集群状态

root@master30 ~ 14:17:58# kubectl get nodes
NAME                   STATUS   ROLES           AGE     VERSION
master30.ningcode.cn   Ready    control-plane   5d      v1.30.2
worker31.ningcode.cn   Ready    <none>          4d23h   v1.30.2
worker32.ningcode.cn   Ready    <none>          4d23h   v1.30.2

2.2 建立 controller / services 命名空间

批处理实验用 controller 命名空间,Service 实验用 services 命名空间(本次日志中 controller 命名空间沿用之前实验创建,services 为本次新建):

root@master30 ~ 16:20:18# kubectl create ns services
namespace/services created
root@master30 ~ 16:20:34# kubectl config set-context --current --namespace services
Context "kubernetes-admin@kubernetes" modified.

提示:切换命名空间后所有 kubectl 操作默认在该命名空间内;跨主题实验建议一个主题一个命名空间,互不干扰。

↑ 回到目录


三、Job 基础:一次性任务

3.1 概念

  • Job 负责运行"跑完就退出"的任务(批处理、计算、初始化数据等),Pod 执行成功后 Job 标记为 Complete
  • 与 Deployment/RS 不同:Deployment 要求 Pod 一直 Running,Job 要求 Pod 正常退出(exit 0)

3.2 命令行创建 Job

# 注意:命令要放在 -- 分隔符之后
root@master30 ~ 14:31:54# kubectl create job myjob --image=busybox -- echo hello k8s job!
job.batch/myjob created

root@master30 ~ 14:31:59# kubectl get all
NAME              READY   STATUS      RESTARTS   AGE
pod/myjob-87cjz   0/1     Completed   0          4s

NAME              STATUS     COMPLETIONS   DURATION   AGE
job.batch/myjob   Complete   1/1           3s         4s

# 查看任务输出
root@master30 ~ 14:32:03# kubectl logs myjob-87cjz
hello k8s job!

坑:kubectl create job myjob --image=... --echo hello 会报 unknown flag: --echo——必须用 -- 把命令与 kubectl 参数分开

3.3 用 YAML 创建 Job(job.yaml)

apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  template:
    metadata:
      name: myjob
    spec:
      containers:
      - name: hello
        image: hub.laoma.cloud/library/busybox
        imagePullPolicy: IfNotPresent
        command: ["echo", "hello k8s job! "]
      restartPolicy: Never        # Job 的 restartPolicy 只支持 Never / OnFailure
root@master30 ~ 14:32:45# kubectl apply -f job.yaml
job.batch/myjob created
root@master30 ~ 14:32:49# kubectl get all
pod/myjob-g9zwt   0/1     Completed   0          11s
job.batch/myjob   Complete   1/1           3s         11s

3.4 查看 Job 状态

root@master30 ~ 14:33:00# kubectl get jobs.batch myjob
NAME    STATUS     COMPLETIONS   DURATION   AGE
myjob   Complete   1/1           3s         18s

COMPLETIONS 显示 1/1 表示 1 个 Pod 成功完成;0/1 表示还没成功。

3.5 同名 Job 不能重复创建

root@master30 ~ 14:31:03# kubectl create job myjob --image=busybox -- echo hello k8s job!
error: failed to create job: jobs.batch "myjob" already exists

# 先删再建
root@master30 ~ 14:31:41# kubectl delete jobs.batch myjob
job.batch "myjob" deleted

↑ 回到目录


四、Job 失败重试:restartPolicy 与 backoffLimit

4.1 故意写错命令,观察失败现象

command 故意写成不存在的 echoxxx

# job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  template:
    spec:
      containers:
      - name: hello
        image: hub.laoma.cloud/library/busybox
        command: ["echoxxx", "hello k8s job! "]
      restartPolicy: Never
root@master30 ~ 14:57:34# kubectl apply -f job.yaml
job.batch/myjob created
root@master30 ~ 14:57:37# kubectl get all
pod/myjob-c6str   0/1     StartError   0          4s
job.batch/myjob   Running   0/1           4s         4s

describe pod 看到核心报错:

State:   Terminated
  Reason:    StartError
  Message:   exec: "echoxxx": executable file not found in $PATH: unknown
  Exit Code: 128

4.2 restartPolicy: Never —— 失败后 Job 重建新 Pod

restartPolicy: Never 时容器失败不会原地重启,Job 控制器会创建新的 Pod 重试(日志中看到 c6str、5d4qz 等一串 Pod):

root@master30 ~ 14:57:54# kubectl get all
pod/myjob-5d4qz   0/1     StartError   0          8s
pod/myjob-c6str   0/1     StartError   0          18s

4.3 restartPolicy: OnFailure —— 同一个 Pod 内重启

# 只把 restartPolicy 改成 OnFailure
      restartPolicy: OnFailure
root@master30 ~ 14:59:39# kubectl apply -f job.yaml
root@master30 ~ 14:59:49# kubectl get all
pod/myjob-pqnqf   0/1     CrashLoopBackOff   1 (3s ago)   3s
root@master30 ~ 15:00:01# kubectl get all
pod/myjob-pqnqf   0/1     RunContainerError   2 (5s ago)   18s

现象:Pod 数量始终是 1,RESTARTS 数值不断增加——容器在同一个 Pod 内反复重启(CrashLoopBackOff)。

4.4 backoffLimit:最多重建几次

spec:
  backoffLimit: 2          # 最多重建 2 次(加上首次,最多 3 个 Pod)
  template:
    spec:
      ...
      restartPolicy: OnFailure
root@master30 ~ 15:01:10# kubectl apply -f job.yaml
# 依次出现 qjccx → 7vfl2 → qz46d 三个 Pod 都失败后
root@master30 ~ 15:02:07# kubectl get all
NAME              READY   STATUS       RESTARTS   AGE
pod/myjob-7vfl2   0/1     StartError   0          24s
pod/myjob-qjccx   0/1     StartError   0          35s
pod/myjob-qz46d   0/1     StartError   0          4s

NAME              STATUS   COMPLETIONS   DURATION   AGE
job.batch/myjob   Failed   0/1           35s        35s

结论:backoffLimit: 2 = 最多重建 2 次(共 3 个 Pod),全部失败后 Job 状态变为 Failed

↑ 回到目录


五、Job 高级参数

5.1 completions:需要成功多少次

默认 Job 成功 1 个 Pod 就 Complete;completions: 2 表示要有 2 个 Pod 成功执行

spec:
  completions: 2
  template:
    spec:
      containers:
      - name: hello
        image: hub.laoma.cloud/library/busybox
        command: ["echo", "hello k8s job! "]
      restartPolicy: Never
root@master30 ~ 15:04:14# kubectl apply -f job.yaml
root@master30 ~ 15:04:33# kubectl get all
pod/myjob-5th5d   0/1     Completed   0          4s
pod/myjob-xlvpj   0/1     Completed   0          7s
NAME              STATUS     COMPLETIONS   DURATION   AGE
job.batch/myjob   Complete   2/2           6s         7s

现象:串行创建 2 个 Pod,都成功后 Job 才 Complete(COMPLETIONS 0/2 → 1/2 → 2/2)。

5.2 parallelism:并行执行

spec:
  completions: 6        # 总共需要 6 个成功
  parallelism: 2        # 每次同时跑 2 个
  template: ...
root@master30 ~ 15:06:22# kubectl apply -f job.yaml
root@master30 ~ 15:06:33# kubectl get all
pod/myjob-2m4gc   0/1     Completed   0          10s
pod/myjob-89mrr   0/1     Completed   0          14s
pod/myjob-8vsrc   0/1     Completed   0          13s
pod/myjob-f4zwp   0/1     Completed   0          16s
pod/myjob-m9fm6   0/1     Completed   0          11s
pod/myjob-qrfsz   0/1     Completed   0          16s
NAME              STATUS     COMPLETIONS   DURATION   AGE
job.batch/myjob   Complete   6/6           8s         16s

效果:每次运行 2 个 Pod,直到总共 6 个 Pod 成功完成。不指定 parallelism 时默认为 1(串行)。

5.3 activeDeadlineSeconds:任务超时

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 10        # 整个 Job 最长运行 10 秒
  template:
    spec:
      containers:
      - name: pi
        image: hub.laoma.cloud/library/perl
        command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
root@master30 ~ 15:07:42# kubectl apply -f job.yaml
job.batch/pi-with-timeout created
root@master30 ~ 15:08:02# kubectl get all
pod/pi-with-timeout-rjpjx   0/1     Terminating   0    24s
NAME                        STATUS   COMPLETIONS   DURATION   AGE
job.batch/pi-with-timeout   Failed   0/1           24s        24s

要点:到达 activeDeadlineSeconds 后,所有运行中 Pod 被终止,Job 状态更新为 Failed、reason 为 DeadlineExceeded;且它的优先级高于 backoffLimit——超时后即使重试次数没到也不再部署新 Pod。

5.4 ttlSecondsAfterFinished:自动清理

spec:
  ttlSecondsAfterFinished: 100     # Job 结束后 100 秒自动删除(含 Pod)
  template:
    spec:
      containers:
      - name: pi
        image: hub.laoma.cloud/library/perl
        command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
root@master30 ~ 15:09:45# kubectl apply -f job.yaml
job.batch/pi-with-ttl created

说明:Job 结束后 100 秒可被 TTL 控制器级联删除(Pod + Job 一起清掉);设为 0 立即清理,不设置则永不自动清除。该机制是 Alpha 功能,需开启 TTLAfterFinished 特性门控。

5.5 计算圆周率的 pi Job(成功案例)

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        imagePullPolicy: IfNotPresent
        command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(200)"]
      restartPolicy: Never
  backoffLimit: 4
root@master30 ~ 14:35:13# kubectl apply -f job-pi.yaml
job.batch/pi created
# 镜像能拉到的话,Pod 完成后 kubectl logs pi-xxxxx 会打印 200 位圆周率

本次实验 pi Job 因内网仓库 192.168.46.201 连接拒绝(perl 镜像拉不到)一直重试,describe pod 可见 ErrImagePull / ImagePullBackOff,删掉 Pod 后 Job 会再建新 Pod 继续重试,直到 backoffLimit 耗尽。

↑ 回到目录


六、CronJob 定时任务

6.1 概念

CronJob 按 Cron 表达式定时创建 Job,适合定时备份、定时清理、定时采集等场景。

6.2 创建每 2 分钟执行一次的 CronJob

root@master30 ~ 15:28:31# kubectl create cronjob mycronjob \
    --image=hub.laoma.cloud/library/busybox \
    --schedule='*/2 * * * *' \
    -- echo hello k8s job!
cronjob.batch/mycronjob created

6.3 观察自动生成的 Job

root@master30 ~ 15:31:32# kubectl get all
NAME                           READY   STATUS      RESTARTS   AGE
pod/mycronjob-29772450-5svhb   0/1     ContainerCreating   0    2m13s
pod/mycronjob-29772452-64pl8   0/1     Completed           0    13s

NAME                      SCHEDULE      TIMEZONE   SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/mycronjob   */2 * * * *   <none>     False     1        13s             2m42s

NAME                           STATUS     COMPLETIONS   DURATION   AGE
job.batch/mycronjob-29772450   Running    0/1           2m13s      2m13s
job.batch/mycronjob-29772452   Complete   1/1           3s         13s

观察点:

  • CronJob 名字带时间戳后缀(mycronjob-29772452),每个调度周期生成一个新的 Job
  • */2 * * * * 表示每 2 分钟执行一次
  • 默认最多保留 3 个已完成的历史 Job(本实验"等待一段时间,最多创建 3 个 job")

↑ 回到目录


七、hostPort 的局限

7.1 先看:直接访问 Pod IP

# 创建单个 Pod(httpd),加 hostPort 前先验证 Pod IP 可访问
root@master30 ~ 16:23:43# kubectl apply -f pod-web.yaml
pod/web created
root@master30 ~ 16:24:03# kubectl get pod -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE
web    1/1     Running   0          16s   10.224.83.166   worker32.ningcode.cn

root@master30 ~ 16:24:09# curl http://10.224.83.166
<html><body><h1>It works!</h1></body></html>

7.2 hostPort:把容器端口映射到宿主机

在 pod-web.yaml 中加 ports(hostPort: 8080):

spec:
  containers:
  - image: hub.laoma.cloud/library/httpd
    name: web
    ports:
    - containerPort: 80
      hostPort: 8080
root@master30 ~ 16:25:24# kubectl apply -f pod-web.yaml
pod/web created
root@master30 ~ 16:25:32# kubectl get pod -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE
web    1/1     Running   0          6s    10.224.83.167   worker32.ningcode.cn

7.3 hostPort 的两个致命问题

问题一:只能通过 Pod 所在节点的 IP 访问。 本次 Pod 调度在 worker32,尝试访问 worker31 的 8080 失败:

root@master30 ~ 16:25:55# curl http://worker31.ningcoe.cn:8080
curl: (6) Could not resolve host: worker31.ningcoe.cn

结论:Pod 只能通过所在主机的 IP 访问,集群外部需要知道 Pod 运行在哪台主机(且 /etc/hosts 拼写也要对:ningcode.cn)。

问题二:同一节点上相同 hostPort 的 Pod 会端口冲突。 Deployment 开 4 个副本(都带 hostPort: 8080)时 2 个 Pending:

root@master30 ~ 16:30:22# kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
web-6f85d979d4-2lb7z   0/1     Pending   0          6s
web-6f85d979d4-475pw   1/1     Running   0          6s
web-6f85d979d4-cznr7   1/1     Running   0          6s
web-6f85d979d4-n9g2c   0/1     Pending   0          6s

root@master30 ~ 16:30:43# kubectl describe pod web-6f85d979d4-2lb7z
Events:
  Warning  FailedScheduling  0/3 nodes are available: 1 node(s) had untolerated taint
  {node-role.kubernetes.io/control-plane: }, 2 node(s) didn't have free ports for the
  requested pod ports.

结论:控制器管理多个相同 hostPort 的 Pod 必然端口冲突(一台机器 8080 只有一个)。所以生产基本不用 hostPort,而是用 Service。

7.4 清理并重新部署(不带 hostPort)

root@master30 ~ 16:31:55# kubectl delete deployments.apps web
deployment.apps "web" deleted
root@master30 ~ 16:32:10# kubectl create deployment web --image=hub.laoma.cloud/library/httpd:2.4.58 --replicas=3
deployment.apps/web created
root@master30 ~ 16:32:23# kubectl get pods --show-labels
NAME                 READY   STATUS    RESTARTS   AGE   LABELS
web-d455dd6b-g2l5j   1/1     Running   0          4s    app=web,pod-template-hash=d455dd6b
web-d455dd6b-rpwzl   1/1     Running   0          4s    app=web,pod-template-hash=d455dd6b
web-d455dd6b-z5xzd   1/1     Running   0          4s    app=web,pod-template-hash=d455dd6b

三个 Pod 全部 Running,标签都是 app=web——这就是接下来 Service 选后端 Pod 的依据。

↑ 回到目录


八、ClusterIP Service:负载均衡

8.1 创建 ClusterIP Service

# --tcp=8080:80 表示 Service 端口 8080 → 容器端口 80
root@master30 ~ 16:32:27# kubectl create service clusterip web --tcp=8080:80
service/web created
root@master30 ~ 16:32:33# kubectl get svc
NAME   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
web    ClusterIP   10.100.254.252   <none>        8080/TCP   17s

8.2 查看 Service 与 Endpoints

root@master30 ~ 16:33:08# kubectl describe service web | grep -e Endpoints -e IP:
IP:                10.100.254.252
Endpoints:         10.224.195.158:80,10.224.83.169:80,10.224.83.170:80

Endpoints 自动收集了 3 个匹配 selector: app=web 的 Pod IP——Service 不直接连 Pod 名,而是通过标签选择后端

8.3 通过 Service 访问(注意端口)

# 端口是 8080,不是 80!不带端口会一直挂起
root@master30 ~ 16:34:12# curl 10.100.254.252:8080
<html><body><h1>It works!</h1></body></html>

8.4 验证负载均衡:三个 Pod 轮流接流量

给每个 Pod 写入不同的 index.html,然后连续请求 60 次:

root@master30 ~ 16:36:52# for pod in $(kubectl get pods -o name | awk -F/ '{print $2}')
> do
>   kubectl exec -it $pod -- bash -c "echo $pod > htdocs/index.html"
> done

root@master30 ~ 16:37:15# for i in {1..60};do curl -s 10.100.254.252:8080;done | sort | uniq -c
     16 web-d455dd6b-g2l5j
     16 web-d455dd6b-rpwzl
     28 web-d455dd6b-z5xzd

60 次请求被分发到 3 个 Pod(16/16/28)——ClusterIP Service 自动做负载均衡

↑ 回到目录


九、Service 进阶:selector 与 expose

9.1 相同标签的 Pod 自动接入 Service

# 手动创建一个带 app=web 标签的独立 Pod
root@master30 ~ 16:37:59# kubectl run web --image=hub.laoma.cloud/library/httpd --labels=app=web
pod/web created

# Service 立刻把请求转发给它(返回默认页 It works!,而非三个 Pod 的名字)
root@master30 ~ 16:38:08# for i in {1..60};do curl -s 10.100.254.252:8080;done | sort | uniq -c
     15 <html><body><h1>It works!</h1></body></html>
     14 web-d455dd6b-g2l5j
     17 web-d455dd6b-rpwzl
     14 web-d455dd6b-z5xzd

Service 只认标签不认来源:任何带 app=web 标签的 Pod 都会自动成为后端。

9.2 rollout restart:重建后标签没变,流量照常

root@master30 ~ 16:38:18# kubectl rollout restart deployment web
deployment.apps/web restarted
root@master30 ~ 16:38:35# for i in {1..60};do curl -s 10.100.254.252:8080;done | sort | uniq -c
     60 <html><body><h1>It works!</h1></body></html>

新 Pod 重建后(之前的 index.html 内容丢失),Service 依然正常转发——用户访问无感知,这就是 Service 的价值。

9.3 expose:一条命令创建 Service

# 错误示范:selector 写成 app2=web2,与实际标签 app=web 不匹配
root@master30 ~ 16:39:27# kubectl expose deployment web --port=8080 --target-port=80 --selector=app2=web2
service/web exposed
root@master30 ~ 16:39:37# kubectl describe svc web | grep -e IP: -e Endpoints
IP:                10.102.92.153
Endpoints:         10.224.195.159:80,10.224.83.173:80

现象:只匹配到 2 个 Endpoints(且都是不相关 Pod)。selector 必须与 Pod 标签一致,否则 Service 找不到正确的后端。

# 正确用法:省略 --selector,自动取 Deployment 的标签
kubectl expose deployment web --port=8080 --target-port=80

# 也可以先 dry-run 导出再改
root@master30 ~ 16:40:16# kubectl create service clusterip web --tcp=8080:80 -o yaml --dry-run=client > svc-web.yml

9.4 svc-web.yml 参考

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
  - port: 8080
    targetPort: 80

↑ 回到目录


十、实战:MySQL + WordPress NodePort

10.1 创建 MySQL Pod 并暴露为 ClusterIP Service

root@master30 ~ 17:31:01# kubectl run mysql \
    --image=hub.laoma.cloud/library/mysql \
    --image-pull-policy=IfNotPresent \
    --env=MYSQL_ROOT_PASSWORD=redhat \
    --env=MYSQL_USER=tom \
    --env=MYSQL_PASSWORD=redhat \
    --env=MYSQL_DATABASE=blog \
    --dry-run=client -o yaml > pod-mysql.yaml
root@master30 ~ 17:31:11# kubectl apply -f pod-mysql.yaml
pod/mysql created

# expose 给集群内其他 Pod 访问(自动创建 ClusterIP Service)
root@master30 ~ 17:31:20# kubectl expose pod mysql --port=3306 --target-port=3306
service/mysql exposed
root@master30 ~ 17:31:32# kubectl get service
NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
mysql   ClusterIP   10.108.123.75   <none>        3306/TCP   8s

10.2 通过 ClusterIP 连接 MySQL

# 宿主机装 mysql-client
root@master30 ~ 17:31:40# apt install -y mysql-client

# 坑1:连错 IP(10.111.69.45 不是 mysql Service 的 IP)
root@master30 ~ 17:31:50# mysql -u tom -predhat -h 10.111.69.45 -e 'show databases;'
ERROR 2003 (HY000): Can't connect to MySQL server on '10.111.69.45:3306' (111)

# 坑2:MySQL 8 的 caching_sha2_password 要求安全连接,--ssl-mode=DISABLED 会报错
root@master30 ~ 17:32:16# mysql -u tom -predhat -h 10.108.123.75 --ssl-mode=DISABLED -e 'show databases;'
ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' reported error:
Authentication requires secure connection.

# 正确:直接连 ClusterIP,不强制关闭 SSL
root@master30 ~ 17:33:42# mysql -u tom -predhat -h 10.108.123.75 -e 'show databases;'
+--------------------+
| Database           |
+--------------------+
| blog               |
| information_schema |
| performance_schema |
+--------------------+

10.3 创建 WordPress 并通过 NodePort 对外提供服务

root@master30 ~ 17:33:43# kubectl run wordpress \
    --image=hub.laoma.cloud/library/wordpress \
    --image-pull-policy=IfNotPresent \
    --env=WORDPRESS_DB_USER=tom \
    --env=WORDPRESS_DB_PASSWORD=redhat \
    --env=WORDPRESS_DB_NAME=blog \
    --env=WORDPRESS_DB_HOST=10.108.123.75
pod/wordpress created

# NodePort 类型:集群外可通过 节点IP:31055 访问
root@master30 ~ 17:34:10# kubectl expose pod wordpress --port=80 --target-port=80 --type NodePort
service/wordpress exposed
root@master30 ~ 17:34:15# kubectl get service
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
mysql       ClusterIP   10.108.123.75   <none>        3306/TCP       2m47s
wordpress   NodePort    10.96.250.25    <none>        80:31055/TCP   4s

访问方式:浏览器打开 http://<任一节点IP>:31055 即可进入 wordpress 安装页。
NodePort 范围:默认 30000–32767;80:31055 表示容器 80 端口映射到节点 31055 端口。

↑ 回到目录


十一、常见错误与排查(重点)

以下全部来自本次实验真实报错,按本文档操作即可避开。

11.1 命令语法类

错误示范 报错 正确写法
kubectl create job myjob --image=... --echo hello unknown flag: --echo 命令放 -- 后:kubectl create job myjob --image=... -- echo hello
kubectl create job myjob --image=busybox -- echo ...(重复创建) jobs.batch "myjob" already exists kubectl delete jobs.batch myjob 再创建
kubectl apply -y job.yaml unknown shorthand flag: 'y' in -y kubectl apply -f job.yaml
kubectl get al the server doesn't have a resource type "al" kubectl get all
for pod in $(kubectlget pods ...) kubectlget: command not found kubectl get pods(命令与子命令间要有空格)
curl http://worker31.ningcoe.cn:8080 Could not resolve host 拼写 ningcode.cn;且 hostPort 只监听 Pod 所在节点
curl 10.100.254.252 一直挂起无响应 Service 端口是 8080:curl 10.100.254.252:8080

11.2 镜像与状态类

现象 原因 处理
Pod ErrImagePull / ImagePullBackOff 私有仓库 192.168.46.201:80 连接拒绝 describe pod 看 Events;换官方镜像或用 nerdctl pull 预热
kubectl logs pi-xhzglcontainer ... is waiting to start: ContainerCreating 容器还没启动完 等 Pod Running 后再看日志
Pod StartError,Message 含 exec: "echoxxx": executable file not found 命令写错/镜像里没有该命令 检查 command 拼写,用 kubectl run ... -- sleep 3600 进容器验证
CrashLoopBackOff / RunContainerError restartPolicy: OnFailure 原地重启 确认是逻辑错误还是环境问题;配合 backoffLimit 限制重试
Job Failed 且出现多个 Pod backoffLimit 达到上限(2 = 最多重建 2 次) 调大 backoffLimit 或修复命令
Job Failed reason DeadlineExceeded 运行超过 activeDeadlineSeconds 任务本身太久,调大超时或优化任务

11.3 Service 类

现象 原因 修复
Deployment 多副本 Pending,Events 报 didn't have free ports 都配了相同的 hostPort,同节点端口冲突 去掉 hostPort,改用 Service
Service 的 Endpoints 为空或不对 selector 与 Pod 标签不匹配(如 app2=web2) kubectl get pods --show-labels 对比,selector 用真实标签
手动创建的 Pod 也被 Service 转发 该 Pod 带相同标签 Service 只认标签;不想接入就换标签或删 Pod
mysql 连接 ERROR 2003 IP 写错(不是 Service 的 ClusterIP) kubectl get svc mysql 查 ClusterIP
mysql 连接 ERROR 2061 ... caching_sha2_password ... secure connection 客户端 --ssl-mode=DISABLED 强制关闭 SSL 去掉该参数,让 MySQL 8 走默认 SSL 认证
NodePort 访问不了 端口不在 30000–32767 或防火墙未放行 kubectl get svc 里的 80:31055 端口;放行节点防火墙

11.4 概念要点

  • Job 的 restartPolicy 只支持 Never / OnFailure:Never 失败重建新 Pod,OnFailure 原地重启
  • backoffLimit 控制最大重建次数;activeDeadlineSeconds 控制总时长且优先于 backoffLimit
  • completions 需要成功次数,parallelism 并行数量,两者组合实现"每次跑 N 个直到 M 个成功"
  • Service 通过 selector 选后端,Pod IP 变化不影响访问;hostPort 只适合单副本、要明确知道调度节点
  • ClusterIP 集群内访问;NodePort 通过 节点IP:30000-32767 对外访问

↑ 回到目录


十二、命令速查表

12.1 Job / CronJob

命令 用途
kubectl create job myjob --image=busybox -- echo hi 命令行创建 Job
kubectl apply -f job.yaml / kubectl delete jobs.batch myjob 文件创建 / 删除
kubectl get jobs.batch / kubectl get all 查看 Job 状态
kubectl logs <pod名> 查看任务输出
kubectl describe pod <pod名> 查看失败原因(Events / Message)
kubectl create cronjob mycronjob --image=busybox --schedule='*/2 * * * *' -- echo hi 创建 CronJob

12.2 Job 常用 spec 字段

字段 作用
restartPolicy: Never / OnFailure Pod 失败策略
backoffLimit: 2 最大重建次数
completions: 6 需要成功多少次
parallelism: 2 同时运行几个
activeDeadlineSeconds: 10 总超时时间
ttlSecondsAfterFinished: 100 结束后自动清理

12.3 Service

命令 用途
kubectl create service clusterip web --tcp=8080:80 创建 ClusterIP Service
kubectl expose pod mysql --port=3306 --target-port=3306 expose Pod
kubectl expose pod wordpress --port=80 --target-port=80 --type NodePort 创建 NodePort
kubectl get svc / kubectl describe svc web 查看 Service 与 Endpoints
kubectl get pods --show-labels 查看 Pod 标签(对照 selector)
kubectl delete svc web 删除 Service

小结

  1. Job 是"跑完即走"的控制器:成功看 COMPLETIONS,失败看 restartPolicy + backoffLimit;生产任务记得配 activeDeadlineSeconds 防止卡死,配 TTL 自动清理。
  2. 失败排查三步kubectl get all 看状态 → describe pod 看 Events/Message → 修复后先删同名 Job 再重建。
  3. hostPort 能不用就不用:端口冲突 + 绑定调度节点两个致命缺陷;对外服务首选 Service。
  4. Service 三件套:ClusterIP(集群内)、NodePort(集群外)、selector(选后端);标签写错 = Endpoints 为空。
  5. NodePort 范围 30000–32767,云服务器别忘了放行防火墙端口。
  6. MySQL 8 认证坑caching_sha2_password 别用 --ssl-mode=DISABLED,直接连 ClusterIP 即可。

本文档由 master30_2026-08-10_14_17_43.log 整理而来,命令提示符保留真实时间戳,全部 YAML 与报错均来自实际操作。

更多推荐