2026-04-24
复习和预习
昨天课堂内容
- secret
- rs
- deployment
课前复习
configure
-
K8s中Secret的核心作用是什么?与ConfigMap的核心区别是什么?
-
K8s中的Secret主要分为哪几种类型?
-
创建Secret的方式有哪些?
-
Pod中引用Secret的方式有哪些?
-
如何查看Secret的明文数据?
-
删除被Pod引用的ConfigMap/Secret后,Pod会发生什么变化?
-
在实际生产中,使用ConfigMap和Secret有哪些最佳实践?(至少列出4点)
controller
-
K8s中ReplicaSet(RS)的核心作用是什么?
-
ReplicaSet的核心配置字段有哪些?分别说明作用。
-
ReplicaSet是否支持Pod的滚动更新?为什么?
-
如何调整ReplicaSet的Pod副本数?
-
K8s中Deployment的核心作用是什么?与ReplicaSet的关系是什么?
-
Deployment的滚动更新核心参数有哪些?
-
如何查看Deployment的历史版本?如何将Deployment回滚到上一个版本?
-
如何查看Deployment的滚动更新状态?如何暂停/恢复Deployment的滚动更新?
-
使用kubectl命令创建Deployment后,如何确认其管理的Pod、RS是否正常创建?
-
Deployment管理的Pod标签被手动修改后,Deployment会如何处理?
综合实验
基于LNMP架构部署blog应用。
具体要求如下:
- 使用mysql:5.7、php-7.2-fpm-wordpress、nginx镜像和wordpress-4.9.4-zh_CN.zip文件部署blog应用。
- 使用pv-pvs方式为mysql和wordpress提供存储。
- pv通过nfs提供。
- blog 应用通过https访问。
- 密码和站点证书用secret存储。
- 站点 vhost 配置使用configmap提供。
- 以上三个应用都使用deployment方式部署。
提示:php-fpm-wordpress 镜像找老师获取。
今天课堂内容
- DaemonSet
- Job
- CronJob
综合实验
实验要求
基于LNMP架构部署blog应用。
具体要求如下:
- 使用mysql:5.7、php-7.2-fpm-wordpress、nginx镜像和wordpress-4.9.4-zh_CN.zip文件部署blog应用。
- 使用pv-pvs方式为mysql和wordpress提供存储。
- pv通过nfs提供。
- blog 应用通过https访问。
- 密码和站点证书用secret存储。
- 站点 vhost 配置使用configmap提供。
提示:php-fpm-wordpress 镜像找老师获取。
实验步骤
部署 NFS 服务
NFS 服务器操作
bash
# 安装 NFS 服务
[root@master30 ~]# apt install -y nfs-kernel-server
# 创建共享目录
[root@master30 ~]# mkdir -p -m 777 /nfsshares/{mysql,blog}
# 配置 NFS 共享(地址已改为10.1.8.30,允许所有节点访问)
[root@master30 ~]# echo "/nfsshares/mysql *(rw,sync,no_root_squash,no_all_squash)
/nfsshares/blog *(rw,sync,no_root_squash,no_all_squash)" >> /etc/exports
# 启动服务
[root@master30 ~]# systemctl restart nfs-server
NFS 客户端操作
bash
## 安装 NFS 客户端
[root@worker31-32 ~]# apt install -y nfs-common
## 验证共享
[root@worker31-32 ~]# showmount -e master30
Export list for master30:
/nfsshares/blog *
/nfsshares/mysql *
准备 pv 和 pvc
bash
[root@master30 ~]# vim pv-pvc.yaml
bash
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-mysql
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
nfs:
path: /nfsshares/mysql
server: 10.1.8.30
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-blog
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
nfs:
path: /nfsshares/blog
server: 10.1.8.30
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-mysql
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-blog
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
bash
[root@master30 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
pvc-blog Bound pv-blog 5Gi RWO <unset> 9s
pvc-mysql Bound pv-mysql 5Gi RWO <unset> 9s
部署 mysql
bash
[root@master30 ~]# kubectl create secret generic mysql --from-literal username=tom --from-literal password_for_user=redhat --from-literal password_for_root=redhat --from-literal db_name=wordpress
[root@master30 certs]# kubectl get secrets mysql
NAME TYPE DATA AGE
mysql Opaque 4 17s
[root@master30 ~]# vim pod-mysql.yaml
yaml
---
apiVersion: v1
kind: Pod
metadata:
name: mysql
labels:
name: mysql
spec:
containers:
- image: docker.io/library/mysql:5.7
imagePullPolicy: IfNotPresent
name: mysql
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql
key: password_for_root
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql
key: username
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql
key: password_for_user
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: mysql
key: db_name
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: pvc-mysql
bash
[root@master30 ~]# kubectl apply -f pod-mysql.yaml
[root@master30 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
mysql 1/1 Running 0 3s
[root@master30 ~]# apt install -y mysql-client
[root@master30 ~]# mysql -utom -predhat -h 10.224.19.1 -e 'show databases;'
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------+
| Database |
+--------------------+
| information_schema |
| wordpress |
+--------------------+
部署 php-fpm
bash
# worker节点导入镜像
[root@worker31 ~]# nerdctl load -i php-7.2-fpm-wordpress.tar
[root@worker32 ~]# nerdctl load -i php-7.2-fpm-wordpress.tar
# 镜像名称是 docker.io/library/php:7.2-fpm-wordpress
[root@master30 ~]# vim pod-php.yaml
yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: php-fpm
name: php-fpm
spec:
containers:
- image: docker.io/library/php:7.2-fpm-wordpress
imagePullPolicy: IfNotPresent
name: php-fpm
volumeMounts:
# PHP 网站根目录
- name: blog-data
mountPath: /usr/share/nginx/html
volumes:
# PHP 网站根目录
- name: blog-data
persistentVolumeClaim:
claimName: pvc-blog
bash
[root@master30 ~]# kubectl apply -f pod-php.yaml
[root@master30 ~]# kubectl get pods php-fpm -o jsonpath={.status.podIP}
10.224.19.44
部署 nginx
创建私钥和证书
bash
#--1--生成私钥
[root@master30 ~]# openssl genrsa -out blog.key 2048
#--2--生成请求文件csr
[root@master30 ~]# openssl req -new -key blog.key -out blog.csr -subj "/C=CN/ST=JS/L=NJ/O=LM/OU=DEVOPS/CN=blog.laoma.cloud/emailAddress=webadmin@laoma.cloud"
# CN的值必须是网站域名
#--3--使用自己的私钥对请求文件签名,以生成证书
[root@master30 ~]# openssl x509 -req -days 3650 -in blog.csr -signkey blog.key -out blog.crt
创建 tls 类型secret
bash
[root@master30 ~]# kubectl create secret tls tls-blog --cert=./blog.crt --key=./blog.key
[root@master30 ~]# kubectl get secrets tls-blog
NAME TYPE DATA AGE
tls-blog kubernetes.io/tls 2 25s
准备虚拟主机配置文件
bash
[root@master30 ~]# vim vhost-blog.conf
bash
server {
listen 80;
listen 443 ssl;
server_name blog.laoma.cloud; # 替换为实际域名
# TLS 配置
ssl_certificate /etc/nginx/tls/tls.crt;
ssl_certificate_key /etc/nginx/tls/tls.key;
# 网站根目录
root /usr/share/nginx/html;
index index.php index.html;
# PHP 转发配置(PHP IP)
location ~ \.php$ {
fastcgi_pass 10.224.19.44:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
}
bash
[root@master30 ~]# kubectl create configmap vhost-blog --from-file=vhost-blog.conf=./vhost-blog.conf
准备 pod-nginx
bash
[root@master30 ~]# vim pod-nginx.yaml
yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
containers:
- image: docker.io/library/nginx:latest
imagePullPolicy: IfNotPresent
name: nginx
volumeMounts:
- name: vhost-blog
mountPath: /etc/nginx/conf.d/vhost-blog.conf
subPath: vhost-blog.conf
- name: tls-blog
mountPath: /etc/nginx/tls
readOnly: true
- name: blog-data
mountPath: /usr/share/nginx/html
volumes:
- name: vhost-blog
configMap:
name: vhost-blog
- name: tls-blog
secret:
secretName: tls-blog
- name: blog-data
persistentVolumeClaim:
claimName: pvc-blog
bash
[root@master30 ~]# kubectl apply -f pod-nginx.yaml
[root@master30 ~]# kubectl get pod nginx
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 11s
[root@master30 ~]# kubectl get pod nginx -o jsonpath={.status.podIP}
10.224.113.173
准备测试页面
bash
[root@master30 ~]# echo Hello World From Nginx > /nfsshares/blog/index.html
[root@master30 ~]# echo "<?php phpinfo(); ?>" > /nfsshares/blog/phpinfo.php
[root@master30 ~]# cat <<'EOF' > /nfsshares/blog/test-mysql.php
<?php
// 数据库配置信息 ------ 请改成你自己的
$host = '10.224.19.45'; // 数据库地址,一般都是 localhost
$user = 'tom'; // 数据库用户名
$pass = 'redhat'; // 数据库密码
$dbname = 'wordpress'; // 要连接的数据库名(必须先创建好)
// 创建连接
$conn = mysqli_connect($host, $user, $pass, $dbname);
// 检测连接是否成功
if (!$conn) {
die("连接失败:" . mysqli_connect_error());
}
// 设置字符集,防止乱码
mysqli_set_charset($conn, 'utf8mb4');
echo "✅ MySQL 数据库连接成功!\n";
// 关闭连接(可选)
mysqli_close($conn);
?>
EOF
测试 Nginx
bash
# 配置解析
[root@master30 ~]# echo '10.224.113.173 blog.laoma.cloud' >> /etc/hosts
# http 站点
[root@master30 ~]# curl http://blog.laoma.cloud/index.html
Hello World From Nginx
[root@master30 ~]# curl -s http://blog.laoma.cloud/phpinfo.php |grep -o 'PHP Version 7'
PHP Version 7
[root@master30 ~]# curl http://blog.laoma.cloud/test-mysql.php
✅ MySQL 数据库连接成功!
# https 站点
[root@master30 ~]# curl -k https://blog.laoma.cloud/index.html
Hello World From Nginx
[root@master30 ~]# curl -sk https://blog.laoma.cloud/phpinfo.php |grep -o 'PHP Version 7'
PHP Version 7
[root@master30 ~]# curl -k https://blog.laoma.cloud/test-mysql.php
✅ MySQL 数据库连接成功!
部署 wordpress 代码
yaml
## 进入 NFS 服务器(IP:10.1.8.30),下载 Wordpress 源码
[root@master30 ~]# unzip wordpress-4.9.4-zh_CN.zip
[root@master30 ~]# cp -a wordpress/* /nfsshares/blog/
## 修改配置文件(连接 MySQL)
[root@master30 ~]# cp /nfsshares/blog/wp-config{-sample,}.php
## 配置数据库信息
[root@master30 ~]# vim /nfsshares/blog/wp-config.php
/** WordPress数据库的名称 */
define('DB_NAME', 'wordpress');
/** MySQL数据库用户名 */
define('DB_USER', 'tom');
/** MySQL数据库密码 */
define('DB_PASSWORD', 'redhat');
/** MySQL主机 */
define('DB_HOST', '10.224.19.45');
访问网站
bash
[root@master30 ~]# kubectl port-forward pod/nginx --address 10.1.8.30 80:80 443:443
Forwarding from 10.1.8.30:80 -> 80
Forwarding from 10.1.8.30:443 -> 443
Kubernetes Controllers
学习参考:控制器
环境准备
bash
[root@master30 ~]# kubectl create ns controllers
[root@master30 ~]# kubectl config set-context --current --namespace controllers
DaemonSet
学习参考:DaemonSet
DaemonSet 介绍
DaemonSet,简写DS,确保全部(或者某些)节点上运行一个 Pod 的副本。 当有新节点加入集群时, 也会在新节点上新增一个 Pod 。 当有节点从集群移除时,移除节点上的 Pod 也会被回收。
DaemonSet 用例
DaemonSet 的一些典型用例:
- 在每个节点上运行集群守护进程,例如存储守护进程 glusterd 和 ceph。
- 在每个节点上运行日志收集守护进程, 例如 flunentd或logstash。
- 在每个节点上运行监控守护进程,例如 Prometheus Node Exporter或 collectd。
DaemonSet 用法
简单的用法:为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。
复杂的用法:为同一种守护进程部署多个 DaemonSet;每个具有不同的标志, 并且对不同硬件类型具有不同的内存、CPU 要求。
DaemonSet 使用
DaemonSet 创建
bash
[root@master30 ~]# vim daemonset.yaml
yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: busybox
spec:
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
- name: busybox
image: docker.io/library/busybox
imagePullPolicy: IfNotPresent
command:
- sleep
- "36000"
bash
[root@master30 ~]# kubectl apply -f daemonset.yaml
DaemonSet 查看
bash
[root@master30 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-5hfv6 1/1 Running 0 102s 10.224.41.132 worker31.laoma.cloud <none> <none>
busybox-jdnsv 1/1 Running 0 102s 10.224.193.66 worker32.laoma.cloud <none> <none>
[root@master30 ~]# kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
busybox 2 2 2 2 2 <none> 118s
DaemonSet 调度
bash
# master节点是默认是不可调度节点
[root@master30 ~]# kubectl describe node master.laoma.cloud |grep Taints
Taints: node-role.kubernetes.io/control-plane:NoSchedule
# 设置master节点可调度
[root@master30 ~]# kubectl taint node master.laoma.cloud node-role.kubernetes.io/control-plane-
[root@master30 ~]# kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
busybox 3 3 3 3 3 <none> 7m43s
# 设置master节点不可调度
[root@master30 ~]# kubectl taint node master.laoma.cloud node-role.kubernetes.io/control-plane:NoSchedule
[root@master30 ~]# kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
busybox 2 2 2 2 2 <none> 8m37s
# pod数量为2个,但是master节点上的busybox pod不会自动删除,也不会计数到这里。
DaemonSet 健壮性
bash
# 删除其中一个pod
[root@master30 ~]# kubectl delete pod busybox-5hfv6
# 自动创建新pod
[root@master30 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-jdnsv 1/1 Running 0 7m6s 10.224.193.66 worker32.laoma.cloud <none> <none>
busybox-wlqqv 1/1 Running 0 8s 10.224.41.133 worker31.laoma.cloud <none> <none>
DaemonSet 删除
删除 DaemonSet 时,默认会删除它创建的所有 Pod,使用**--cascade=orphan**选项,将保留DaemonSet 创建 Pod。
bash
[root@master master]# kubectl delete daemonsets.apps busybox
daemonset.apps "busybox" deleted
K8s 集群中 DS
- Kubernetes 使用 DaemonSet 控制器运行系统组件,例如kube-proxy、calico-node。
bash
[root@master30 ~]# kubectl get daemonsets.apps --namespace kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
calico-node 3 3 3 3 3 kubernetes.io/os=linux 4h12m
kube-proxy 3 3 3 3 3 kubernetes.io/os=linux 4h56m
- 分析calico-node 的yaml文件:
yaml
kind: DaemonSet
apiVersion: apps/v1
metadata:
name: calico-node
namespace: kube-system
labels:
k8s-app: calico-node
spec:
selector:
matchLabels:
k8s-app: calico-node
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
k8s-app: calico-node
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ''
spec:
nodeSelector:
kubernetes.io/os: linux
hostNetwork: true
containers:
- name: calico-node
image: docker.io/calico/node:v3.28.0
注意: 完整配置文件内容要更复杂一些, 为了方便学习DaemonSet, 这里只保留了最重要的内容。
生产级示例
采集节点日志-Fluent
yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit-ds
namespace: kube-system
spec:
selector:
matchLabels:
app: fluent-bit
template:
metadata:
labels:
app: fluent-bit
spec:
containers:
- name: fluent-bit
image: cr.fluentbit.io/fluent/fluent-bit:latest
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
采集节点监控指标-Node Exporter
yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter-ds
namespace: monitoring
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
containers:
- name: node-exporter
image: prom/node-exporter:latest
ports:
- containerPort: 9100
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
- name: sys
mountPath: /host/sys
readOnly: true
volumes:
- name: proc
hostPath:
path: /proc
- name: sys
hostPath:
path: /sys
Job
学习参考:Job
Job 介绍
- Job 用于运行一次性任务。
- 如果pod运行任务失败,则创建新的pod继续运行任务,直到任务运行完成,也就是pod中任务退出代码为0, Job结束。
- 在job运行过程中,如果托管pod的节点发生故障,Job pod将被自动重新安排到另一个节点。
- 删除 Job 的操作会清除所创建的全部 Pod。
- 挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。
如果你想按某种排期表(Schedule)运行 Job(单个任务或多个并行任务),请参阅 CronJob。
Job 用例
简单的使用场景:
- 执行数据库清理
- 备份 Kubernetes 集群
Job 实践
Job 基本管理
bash
[root@master30 ~]# kubectl create job -h
Create a job with the specified name.
Examples:
# Create a job
kubectl create job my-job --image=busybox
# Create a job with command
kubectl create job my-job --image=busybox -- date
......
Usage:
kubectl create job NAME --image=image [--from=cronjob/name] -- [COMMAND]
[args...] [options]
示例1:
bash
[root@master30 ~]# kubectl create job myjob --image=docker.io/library/busybox -- echo hello k8s job!
[root@master30 ~]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/myjob-pdrwv 0/1 Completed 0 11m
NAME COMPLETIONS DURATION AGE
job.batch/myjob 1/1 20s 11m
[root@master30 ~]# kubectl logs myjob-pdrwv
hello k8s job!
# 删除 Job 的操作会清除所创建的全部 Pod
# 使用--cascade=orphan选项,可以保留job创建的pod
[root@master30 ~]# kubectl delete jobs.batch myjob
通过 yaml 文件创建job。
bash
[root@master30 ~]# vim job.yaml
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
metadata:
name: myjob
spec:
containers:
- name: hello
image: docker.io/library/busybox
imagePullPolicy: IfNotPresent
command: ["echo", "hello k8s job! "]
restartPolicy: Never
bash
[root@master30 ~]# kubectl apply -f job.yaml
**示例2:**计算pi,保留小数点200位。
bash
[root@master30 ~]# vim job-pi.yaml
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: docker.io/library/perl
imagePullPolicy: IfNotPresent
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(200)"]
restartPolicy: Never
backoffLimit: 4
bash
[root@master30 ~]# kubectl apply -f job-pi.yaml
# 等几分钟
[root@master30 ~]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/pi-2hn9n 0/1 Completed 0 3m13s
NAME COMPLETIONS DURATION AGE
job.batch/pi 1/1 76s 3m13s
[root@master30 ~]# kubectl logs pi-qzxv4
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303820
restartPolicy
restart 策略只能是:
- Nerver :只要任务没有完成,就会创建新的 pod,直到 job 完成,所以有可能会产生多个pod。
- OnFailure :只要任务没有完成,就会重启 pod,直到job完成。
我们做个试验, 修改job.yaml, 故意引入一个错误 :echo 修改为 echoxxx。
bash
[root@master30 ~]# vim job.yaml
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
metadata:
name: myjob
spec:
containers:
- name: hello
image: docker.io/library/busybox
imagePullPolicy: IfNotPresent
command: ["echoxxx", "hello k8s job! "]
restartPolicy: Never
再次应用,验证。
bash
[root@master30 ~]# kubectl apply -f job.yaml
[root@master30 ~]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/myjob-6hp5t 0/1 StartError 0 33s
pod/myjob-gczbz 0/1 StartError 0 2s
pod/myjob-n4bpv 0/1 StartError 0 22s
NAME COMPLETIONS DURATION AGE
job.batch/myjob 0/1 33s 33s
# 可以看到有多个Pod, 状态均不正常。
# 查看某个 pod 详细信息
[root@master30 ~]# kubectl describe pod myjob-6hp5t
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 50s default-scheduler Successfully assigned laoma/myjob-6hp5t to worker32.laoma.cloud
Normal Pulled 50s kubelet Container image "busybox" already present on machine
Normal Created 50s kubelet Created container hello
Warning Failed 50s kubelet Error: failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "echoxxx": executable file not found in $PATH: unknown
日志显示没有可执行程序, 符合我们的预期。
问题现象: 为什么会看到这么多失败的Pod?
解释: 当第一个Pod启动时, 容器失败退出, 根据 restartPolicy: Never, 此失败容器不会被重启, 但 Job 默认预期完成的Pod数量是1, 目前COMPLETIONS为0, 不满足, 所以 Job 会创建新的Pod, 直到 COMPLETIONS 为1。 对于我们这个例子, SUCCESSFUL 永远也到不了1, 所以Job 会一直创建新的Pod。 为了终止这个行为, 我们删除Job 。
清理 job
bash
[root@master30 ~]# kubectl delete jobs.batch myjob
如果将 restartPolicy 设置为 OnFailure 会怎么样?
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
metadata:
name: myjob
spec:
containers:
- name: hello
image: docker.io/library/busybox
imagePullPolicy: IfNotPresent
command: ["echoxxxx", "hello k8s job! "]
restartPolicy: OnFailure
再次应用,验证。
bash
[root@master30 ~]# kubectl apply -f job.yaml
[root@master30 ~]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/myjob-xbwxh 0/1 CrashLoopBackOff 1 68s
NAME COMPLETIONS DURATION AGE
job.batch/myjob 0/1 68s 68s
# pod执行失败后,会重启。
# pod 数量总是1,RESTARTS数值不断增加。
backoffLimit
如果Job执行失败,我们可以指定job执行失败最大次数。
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
backoffLimit: 2
template:
metadata:
name: myjob
spec:
containers:
- name: hello
image: docker.io/library/busybox
imagePullPolicy: IfNotPresent
command: ["echoxx", "hello k8s job! "]
restartPolicy: Never
测试结果:最多重建2次。
bash
[root@master30 ~]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/myjob-bdgxq 0/1 StartError 0 30s
pod/myjob-ffth2 0/1 StartError 0 83s
pod/myjob-abcde 0/1 StartError 0 83s
NAME COMPLETIONS DURATION AGE
job.batch/myjob 0/1 83s 83s
completions
我们还可以通过 completions 指定 Pod 执行完成多少次,才算Job执行完成。
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
completions: 2
template:
metadata:
name: myjob
spec:
containers:
- name: hello
image: docker.io/library/busybox
imagePullPolicy: IfNotPresent
command: ["echo", "hello k8s job! "]
restartPolicy: Never
上面配置的含义是: 每次运行两个Pod, 直到总共有6个Pod成功完成。
bash
[root@master30 ~]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/myjob-72zgz 0/1 Completed 0 4s
pod/myjob-8brdq 0/1 Completed 0 8s
NAME COMPLETIONS DURATION AGE
job.batch/myjob 2/2 12s 12s
如果不指定completions, 默认值均为1。
parallelism
有时我们希望Job同时运行多个Pod, 提高Job的执行效率,通过parallelism设置并行Pod数量 。
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
completions: 6
parallelism: 2
template:
metadata:
name: myjob
spec:
containers:
- name: hello
image: docker.io/library/busybox
imagePullPolicy: IfNotPresent
command: ["echo", "hello k8s job! "]
restartPolicy: Never
bash
[root@master30 ~]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/myjob-72zgz 0/1 Completed 0 4s
pod/myjob-8brdq 0/1 Completed 0 8s
pod/myjob-8l5cx 0/1 Completed 0 8s
pod/myjob-9gkt8 0/1 Completed 0 12s
pod/myjob-wcwwh 0/1 Completed 0 12s
pod/myjob-xs6pd 0/1 Completed 0 4s
NAME COMPLETIONS DURATION AGE
job.batch/myjob 6/6 12s 12s
效果: 每次运行2个Pod, 直到总共有6个Pod成功完成。
如果不指定parallelism, 默认值均为1。
上面的例子只是为了演示Job的并行特性, 实际用途不大。 不过现实中确实存在很多需要并行处理的场景。 比如批处理程序, 每个副本(Pod) 都会从任务池中读取任务并执行, 副本越多, 执行时间就越短, 效率就越高。这种类似的场景都可以用Job来实现。
activeDeadlineSeconds
一旦 Job 运行时间达到该值,其所有运行中的 Pod 都会被终止 ,并且 Job 的状态更新为 type: Failed 及 reason: DeadlineExceeded。该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: pi-with-timeout
spec:
backoffLimit: 5
activeDeadlineSeconds: 10
template:
spec:
containers:
- name: pi
image: docker.io/library/perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
Job 的 .spec.activeDeadlineSeconds 优先级高于其 .spec.backoffLimit 设置。 因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达 activeDeadlineSeconds 所设的时限即不再部署额外的 Pod,即使其重试次数还未达到 backoffLimit 所设的限制。
ttlSecondsAfterFinished
通过设置 Job 的 .spec.ttlSecondsAfterFinished 字段,可以让该控制器清理掉已结束的资源。
TTL 控制器清理 Job 时,会级联式地删除 Job 对象。 换言之,它会删除所有依赖的对象,包括 Pod 及 Job 本身。 注意,当 Job 被删除时,系统会考虑其生命周期保障,例如其 Finalizers。
例如:
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: pi-with-ttl
spec:
ttlSecondsAfterFinished: 100
template:
spec:
containers:
- name: pi
image: docker.io/library/perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
Job pi-with-ttl 在结束 100 秒之后,可以成为被自动删除的对象。
如果该字段设置为 0,Job 在结束之后立即成为可被自动删除的对象。 如果该字段没有设置,Job 不会在结束之后被 TTL 控制器自动清除。
注意这种 TTL 机制仍然是一种 Alpha 状态的功能特性,需要配合 TTLAfterFinished 特性门控使用。有关详细信息,可参考 TTL 控制器的文档。
