DePloyment 无状态应用
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: APP_NAME
namespace: kube-system
spec:
replicas: 1
template:
metadata:
labels:
app: APP_NAME
spec:
containers:
- name: CONTAINER_NAME_1
image: docker.io/image_name_1:latest
env:
- name: TZ # 设置容器的时区为东八区
value: Asia/Shanghai
imagePullPolicy: Always
restartPolicy: Always
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "1024Mi"
cpu: "800m"
volumeMounts:
- name: weblog
mountPath: /logs
ports:
- containerPort: 8091
- name: CONTAINER_NAME_2
image: docker.io/image_name_2:latest
imagePullPolicy: IfNotPresent
restartPolicy: Always
readinessProbe:
httpGet:
path: /favicon.ico
port: 80
scheme: HTTP
initialDelaySeconds: 15
failureThreshold: 3
periodSeconds: 30
timeoutSeconds: 20
livenessProbe:
httpGet:
path: /favicon.ico
port: 80
scheme: HTTP
initialDelaySeconds: 15
failureThreshold: 3
periodSeconds: 30
timeoutSeconds: 20
volumeMounts:
- name: web
mountPath: /www
ports:
- containerPort: 443
volumes:
- name: weblog
nfs:
server: 1.1.1.1
path: "/path/logs"
- name: web
nfs:
server: 1.1.1.1
path: "/path/web"
k8s pod 经常挤在同一节点或因为没配置资源限制某一节点内存已经接近临界值仍不会迁移。而且在手动删除容器重启时还会出现再次在该节点重建的情况,可以通过pod的反亲和特性来使其在手动删除pod时不会在同一节点重建
pod 亲和
StatefulSet 有状态应用
StatefulSet的特性:
StatefulSet给每个Pod提供固定名称,Pod名称增加从0-N的固定后缀,Pod重新调度后Pod名称和HostName不变。
StatefulSet通过Headless Service给每个Pod提供固定的访问域名,Service的概念会在Service中详细介绍。
StatefulSet通过创建固定标识的PVC保证Pod重新调度后还是能访问到相同的持久化数据。
创建Statefulset需要一个Headless Service用于Pod访问,Service的概念会在Service中详细介绍,这里先介绍Headless Service的创建方法。
使用如下文件描述Headless Service,其中:
spec.clusterIP:必须设置为None,表示Headless Service。
spec.ports.port:Pod间通信端口号。
spec.ports.name:Pod间通信端口名称。
创建Headless Service
创建Statefulset
Init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。
其特点和作用可以归纳如下:
initContainers在主容器启动之前按顺序执行,全部执行成功后才会启动主容器。
initContainers和主容器在不同的容器中隔离运行,有不同的文件系统视图。
initContainers可以有多个,之间是串行执行的关系。
如果任何一个initContainer失败了,都会导致Pod启动失败。
initContainers可以用于执行一些预初始化工作,如数据或文件拉取,配置或验证等。
initContainers提供了一种类似故障漏斗的机制,必须前置任务成功后才能继续后续任务。
initContainers可以在Pod重启的时候再次执行 Initialization 逻辑。
这里是一个使用 initContainers 的示例:
这个示例中有两个 initContainers:
init-myservice 容器会等待 myservice 这个服务准备就绪
init-properties 容器会等待 app.properties 配置文件准备就绪
只有两个 init 容器都执行成功了,myapp-container 主容器才会启动。
init容器与主容器之间主要通过两种方式进行交互和数据传递:
通过volume挂载Volume来传递数据 init容器和主容器可以挂载同一个Volume,init容器在Volume中写入数据,主容器启动后可以读取这些数据,以此实现两者之间的数据传递。
通过文件或目录的写操作 由于init容器与主容器共享一个Pod上下文和文件系统视图,init容器对Pod内目录的修改操作,也会反映到主容器中。
举个例子,init容器在某目录下创建了一个文件,那么主容器就可以读取到这个文件,两者实现了交互。
需要注意的是,init容器必须保证操作的文件或目录不会被主容器inplace覆盖。另外,如果Volume支持可写(ReadWriteMany),也可以在Volume中进行交互。
如果init容器修改了基础镜像中的某个配置的值,而这个配置对主容器也是可见的,那么主容器中这个配置的值会是init容器修改后的值。
主要原因是:
init容器与主容器共享同一个Pod的文件系统视图。 当init容器启动时,它实际上启动的是基础镜像的一个容器实例。init容器对镜像中文件所做的任何修改,都会反映到文件系统视图中。 主容器启动时,它看到的文件系统视图已经被init容器做过修改。
PV、PVC和StorageClass
如果要求Pod重新调度后仍然能使用之前读写过的数据,就只能使用网络存储了,网络存储种类非常多且有不同的使用方法,通常一个云服务提供商至少有块存储、文件存储、对象存储三种,如华为云的EVS、SFS和OBS。Kubernetes解决这个问题的方式是抽象了PV(PersistentVolume)和PVC(PersistentVolumeClaim)来解耦这个问题,从而让使用者不用关心具体的基础设施,当需要存储资源的时候,只要像CPU和内存一样,声明要多少即可。
PV:PV描述的是持久化存储卷,主要定义的是一个持久化存储在宿主机上的目录,比如一个NFS的挂载目录。
PVC:PVC描述的是Pod所希望使用的持久化存储的属性,比如,Volume存储的大小、可读写权限等等。 Kubernetes管理员设置好网络存储的类型,提供对应的PV描述符配置到Kubernetes,使用者需要存储的时候只需要创建PVC,然后在Pod中使用Volume关联PVC,即可让Pod使用到存储资源,它们之间的关系如下图所示。 
如果使用网络存储,则直接使用对应的协议即可。如果要使用k8s的CSI来进行调度存储,可以用本地存储类或第三方存储类(如rook、ceph等)来管理存储。需要弄清楚几个问题
StorageClass :存储类,只有安装了存储类,才能创建跟存存储类一样的PV,PVC。一般用本地存储
PV:先在宿主机上划出一个指定大小的空间用来准备给PVC申请,
PVC:在已经划分出来的PV空间中申请可用的存储空间,一般一个PV尽量只给一个PVC。
StorageClass 存储类
存储类分静态和动态两种
动态存储类(Dynamic Provisioning): 动态存储类允许Kubernetes自动创建PersistentVolume(PV)以满足PersistentVolumeClaim(PVC)的需求。当PVC被创建并指定使用某个动态存储类时,Kubernetes会按照存储类的定义自动创建一个符合要求的PV。这样,集群管理员无需手动创建PV,而是让Kubernetes根据需要动态创建。
以下是一个动态存储类的示例:
上面的示例使用了AWS的EBS(Elastic Block Store)作为存储后端,当PVC使用该存储类时,Kubernetes会自动创建对应的EBS卷。
静态存储类(Static Provisioning): 静态存储类是手动创建的,它与预先存在的PersistentVolume相关联。在这种情况下,集群管理员需要手动创建PV,并为它们定义一个静态存储类。PVC可以选择使用这个静态存储类,并将其绑定到预先创建的PV上。
以下是一个静态存储类的示例:
总结: 动态存储类: 允许Kubernetes在需要时自动创建PV,无需手动干预。它通常与云服务提供商的存储解决方案一起使用。
静态存储类: 需要手动创建PV,并将其绑定到静态存储类。这种方法通常用于本地存储或其他不支持动态创建PV的场景。
Kubernetes提供了CSI接口(Container Storage Interface,容器存储接口),基于CSI这套接口,可以开发定制出CSI插件,从而支持特定的存储,达到解耦的目的。
pvc示例
调用静态存储类
在StatefulSet 还可以通过定义PVC模板,自动创建pod所需的PVC,前提是如果使用的静态存储类,则需要先手动创建足够数量的匹配PV,该PVC模板才会生效,否则PVC会一直处于pending状态
下面是在StatefulSet中的PVC模板配置
ConfigMap 是一种 API 对象,用来将非机密性的数据保存到键值对中。使用时, Pods 可以将其用作环境变量、命令行参数或者存储卷中的配置文件。
注意: ConfigMap 并不提供保密或者加密功能。 如果你想存储的数据是机密的,请使用 Secret, 或者使用其他第三方工具来保证你的数据的私密性,而不是用 ConfigMap。
ConfigMap 配置示例
ConfigMap调用示例
当卷中使用的 ConfigMap 被更新时,所投射的键最终也会被更新。 kubelet 组件会在每次周期性同步时检查所挂载的 ConfigMap 是否为最新。 不过,kubelet 使用的是其本地的高速缓存来获得 ConfigMap 的当前值。 高速缓存的类型可以通过 KubeletConfiguration 结构 的 ConfigMapAndSecretChangeDetectionStrategy 字段来配置。
配置不可变更的ConfigMap
对于大量使用 ConfigMap 的 集群(至少有数万个各不相同的 ConfigMap 给 Pod 挂载)而言,禁止更改 ConfigMap 的数据有以下好处:
保护应用,使之免受意外(不想要的)更新所带来的负面影响。
通过大幅降低对 kube-apiserver 的压力提升集群性能,这是因为系统会关闭 对已标记为不可变更的 ConfigMap 的监视操作。
此功能特性由 ImmutableEphemeralVolumes 特性门控 来控制。你可以通过将 immutable 字段设置为 true 创建不可变更的 ConfigMap。
示例如下:
Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 SSH 密钥。 将这些信息放在 secret 中比放在 Pod 的定义或者 容器镜像 中来说更加安全和灵活。
注意: Kubernetes Secret 默认情况下存储为 base64-编码的、非加密的字符串。 默认情况下,能够访问 API 的任何人,或者能够访问 Kubernetes 下层数据存储(etcd) 的任何人都可以以明文形式读取这些数据。 为了能够安全地使用 Secret,我们建议你(至少):
kubernetes.io/service-account-token
kubernetes.io/dockerconfigjson
~/.docker/config.json 文件的序列化形式
bootstrap.kubernetes.io/token
通过为 Secret 对象的 type 字段设置一个非空的字符串值,你也可以定义并使用自己 Secret 类型。如果 type 值为空字符串,则被视为 Opaque 类型。 Kubernetes 并不对类型的名称作任何限制。不过,如果你要使用内置类型之一, 则你必须满足为该类型所定义的所有要求。
TLS Secret
服务账号令牌Secret
基本身份认证 Secret
SSH 身份认证 Secret
Secret调用
readinessProbe:就绪探针,用于判断容器应用是否启动完成
livenessProbe:存活探针,用于判断容器中的应用是否正常运行
initialDelaySeconds:pod启动后首次进行检查的等待时间,单位“秒”
failureThreshold:探测失败的重试次数
periodSeconds:探测的间隔时间,单位“秒”
timeoutSeconds:探测请求等待响应的超时时间,单位“秒”
定义存活命令
periodSeconds 字段指定了 kubelet 应该每 5 秒执行一次存活探测。 initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 5 秒。 kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。 如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。
定义一个存活态 HTTP 请求接口
periodSeconds 字段指定了 kubelet 每隔 3 秒执行一次存活探测。 initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 3 秒。 kubelet 会向容器内运行的服务(服务会监听 8080 端口)发送一个 HTTP GET 请求来执行探测。 如果服务器上 /healthz 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。 如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。
官方示例
示例2 periodSeconds 字段指定了 kubelet 每隔 30 秒执行一次存活探测。 initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 15 秒。 failureThreshold字段告诉kubelet可以失败几次。kubelet 会向容器内运行的服务(服务会监听 8080 端口)发送一个 HTTP GET 请求来执行探测。 如果服务器上 /ver.txt 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。 如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。
定义 TCP 的存活探测
第三种类型的存活探测是使用 TCP 套接字。 通过配置,kubelet 会尝试在指定端口和容器建立套接字链接。 如果能建立连接,这个容器就被看作是健康的,如果不能则这个容器就被看作是有问题的。
定义健康检查脚本
health.sh 脚本,将以下内容写入health.sh脚本,并打包进镜像内
健康脚本应用示例,在deployment.yaml文件中如下区域添加以下配置
注意事项: 如果未能在调度时间内创建 CronJob,则计为错过。 例如,如果 concurrencyPolicy (并发策略)被设置为 Forbid(禁止),并且当前有一个调度仍在运行的情况下, 试图调度的 CronJob 将被计算为错过。
有时我们的定时任务脚本在完成后并不是正常退出,而是通过kill等方式强制结束时,k8s会自动重建pod试图恢复服务,如果不希望k8s恢复本次pod,就可以通过配置restartPolicy: Never和backoffLimit: 0来防止其重建pod。