首先kubectl向kubeapi接口发送指令后,kubeapi会调度到Kubelet(这过程通过etcd存储),Kubelet去操作CRI,CRI完成容器的初始化,在初始化的过程中会先启动一个Pause的基础容器(负责网络以及存储卷共享),然后进行多个init C初始化,进入Main C 主容器运行,Main C退出时可执行STOP,执行完整个Pod生命周期结束。
readlines为什么不是顶头,靠左端?因为允许定义在容器运行多少秒以后再进行探测
readlines:根据命令、TCP连接、HTTP协议获取状态,判断这个服务是否已可用,如果可用再把运行状态改成Running,能暴露出去提供外网访问
Liveness:它会伴随整个Main C主容器的生命周期,当主容器里面的进程跟Liveness探测出现不一致的情况时,主容器有问题不能正常提供外网访问,然后就执行重启或删除命令。
Pod能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的Init容器
Init容器与普通的容器非常像,除了如下两点:
如果Pod的Init容器失败,Kubernetes会不断地重启该Pod,直到Init容器成功为止。然而,如果Pod对应的restartPolicy为Never,它不会重新启动
因为Init容器具有与应用程序容器分离的单独镜像,所以它们的启动相关代码具有如下优势:
init C还可以用于探测mysql是否正常,如果正常则退出init容器,启动Apache。
而不会因为Apache+PHP启动稍微快一点,mysql延迟启动导致整个pod不断重启。
(针对依赖性比较强的服务而言)
Pod Phase
kubectl get pod $podname -o yaml
kubectl describe pod
避免容器进程被终止避免Pod被驱逐
设置合理的resources.memory limits防止容器进程被OOMKill。
设置合理的emptydir.sizeLimit并且确保数据写入不超过emptyDir的限制,防止Pod被驱逐。
定义Guaranteed类型的资源需求来保护你的重要Pod.
认真考量Pod需要的真实需求并设置limit和resource,这有利于将集群资源利用率控制在合理范围并减少Pod被驱逐的现象。
尽量避免将生产Pod设置为BestEffort,但是对测试环境来讲,BestEffort Pod能确保大多数应用不会因为资源不足而处于Pending状态。
Burstable 适用于大多数场景。
有时候会存在特殊情况,比如服务A启动时间很慢,需要60s。这个时候如果还是用上面的探针就会进入死循环,因为上面的探针10s后就开始探测,这时候我们服务并没有起来,发现探测失败就会触发restartPolicy。这时候有的朋友可能会想到把initialDelay调成60s不就可以了?但是我们并不能保证这个服务每次起来都是60s,假如新的版本起来要70s,甚至更多的时间,我们就不好控制了。有的朋友可能还会想到把失败次数增加。这在启动的时候是可以解决我们目前的问题,但是如果这个服务挂了呢?如果failureThreshold=1则10s后就会报警通知服务挂了,如果设置了failureThreshold=5,那么就需要5*10s=50s的时间,在现在大家追求快速发现、快速定位、快速响应的时代是不被允许的。在这时候我们把startupProbe和livenessProbe结合起来使用就可以很大程度上解决我们的问题。
livenessProbe:
httpGet:
path: /test
prot: 80
failureThreshold: 1
initialDelay:10
periodSeconds: 10
startupProbe:
httpGet:
path: /test
prot: 80
failureThreshold: 10
initialDelay:10
periodSeconds: 10
上面的配置是只有startupProbe探测成功后再交给livenessProbe。我们startupProbe配置的是10s*10+10s,也就是说只要应用在110s内启动都是OK的,而且应用挂掉了10s就会发现问题
探针属性(可以设置探针的时间)
ReadinessGates(可以通过k8s外部控制器控制是否ready)
Post-start和Pre-Stop
容器关闭过程:pre-stop执行完后,先发sigterm(pod会处在terminating状态),再发sigkill
postStart示例:
apiVersion: v1
kind: Pod
metadata:
name: poststart
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
pre-stop示例:
apiVersion: v1
kind: Pod
metadata:
name: prestop
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
preStop:
exec:
command: [ "/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done" ]
terminating状态60秒后会自动退出示例:
apiVersion: v1
kind: Pod
metadata:
name: no-sigterm
spec:
terminationGracePeriodSeconds: 60
containers:
- name: no-sigterm
image: centos
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello; sleep 10;done"]
terminationGracePeriodSeconds
Terminating Pod的误用
bash/sh会忽略SIGTERM信号量,因此kill -SIGTERM会永远超时,若应用使用bash/sh作为Entrypoing,则应避免过长的graceperiod,避免更多的无效等待
terminating pod的经验分享
优雅的初始化进程应该
编写K8s部署脚本将httpserver部署到k8s集群,以下思考的维度:
优雅启动
deploy 中的 livenessProbe 提供优雅启动功能,只有当 8080 端口的 http get 请求 url 127.0.0.1/healthz 返回 200 时,pod 才会被 k8s 设置成 ready 状态,然后才会接收外部流量
livenessProbe:
httpGet:
### this probe will fail with 404 error code
### only httpcode between 200-400 is retreated as success
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
探活
deploy 中配置的 readinessProbe 用于探活,一旦 /healthz 这个接口请求不通,或者返回非 200~400 的状态码,则 k8s 就会将这个pod调度为非 ready 状态,流量就不会请求到这个 pod 上了
# 探活
readinessProbe:
httpGet:
### this probe will fail with 404 error code
### only httpcode between 200-400 is retreated as success
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 2
资源需求和 QoS 保证
日常运维需求,日志等级
日常日志查看,其中 httpserver-565798b9f9-4rghf 是 pod 的名称,由于 httpserver 将日志打印到控制台中所以可以直接通过 logs 命令查看
kubectl logs -f httpserver-565798b9f9-4rghf
但是在pod出现异常退出时,日志也就销毁了,这种方法就无法查看到日志了
常用的日志解决方案是采用 ELK 方案,搭建 ElasticSearch 集群,采用 filebeat(最初是 logstash,由于内存消耗比较大,所以现在一般用 filebeat 替代)采集日志存储至 ES 中,最后通过 Kibana 查询和展示。
filebeat 可以采用 sidecar 的方式挂载到业务容器中,也可通过 DeamonSet 的方式启动之后进行收集。
配置和代码分离
通过 ConfigMap 配置 httpserver 的配置
envFrom:
- configMapRef:
name: httpserver-env-cm
volumeMounts:
- name: config-volume
mountPath: /etc/httpserver/httpserver.properties
ConfigMap + viper 实现 httpserver 热加载配置
viperInstance.WatchConfig()
viperInstance.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Detect config change: %s \n", e.String())
log.Warn("Config file updated.")
viperLoadConf(viperInstance) // 加载配置的方法
})
当 ConfigMap 发生变化时,会被 viperInstance.WatchConfig() 监控到,然后会调用 OnConfigChange 函数中的匿名函数,重新加载配置
Deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpserver
namespace: mxs
labels:
app: httpserver
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
# maxSurge: 最大激增数, 指更新过程中, 最多可以比replicas预先设定值多出的pod数量, 可以为固定值或百分比(默认25%), 更新过程中最多会有replicas + maxSurge个pod
maxSurge: 2
# maxUnavailable: 最大无效数, 指更新过程中, 最多有几个pod处于无法服务状态, 当maxSurge不为0时, 此栏位也不可为0, 整个更新过程中, 会有maxUnavailable个pod处于Terminating状态
maxUnavailable: 1
# minReadySeconds: 容器内应用的启动时间, pod变为run状态, 会在minReadySeconds后继续更新下一个pod. 如果不设置该属性, pod会在run成功后, 立即更新下一个pod.
minReadySeconds: 15
selector:
matchLabels:
app: httpserver
template:
metadata:
labels:
app: httpserver
spec:
containers:
- name: httpserver
image: tjudream/httpserver:v5
command: [/httpserver]
envFrom:
- configMapRef:
name: httpserver-env-cm
volumeMounts:
- name: config-volume
mountPath: /etc/httpserver/
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 500m
memory: 512Mi
# 优雅启动
livenessProbe:
httpGet:
### this probe will fail with 404 error code
### only httpcode between 200-400 is retreated as success
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
# 探活
readinessProbe:
httpGet:
### this probe will fail with 404 error code
### only httpcode between 200-400 is retreated as success
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 2
volumes:
- name: config-volume
configMap:
# Provide the name of the ConfigMap containing the files you want
# to add to the container
name: httpserver-conf-cm
在kurbernetes中,pod是应用程序的载体,可以通过pod的ip来访问应用程序,但是pod的ip地址不是固定的,这也就意味着不方便直接采用pod的ip对服务进行访问
为了解决这个问题,kubernetes提供了Service资源,Service会对提供同一个服务的多个pod进行聚合,并且提供一个统一的入口地址。通过访问Sevice的入口地址就能访问到后面的pod服务。
Service在很多情况下只是一个概念,真正起作用的其实是 kube-proxy 服务进程,每个Node节点上都运行着一个 kube-proxy 服务进程。
当创建Service的时候会通过api-server向etcd写入创建的service的信息,而kube-proxy会基于监听的机制发现这种service的变动,然后它会将最新的service信息转换成对应的访问规则(iptables规则或ipvs规则)。
服务发布
需要把服务发布至集群内部或者外部,服务的不同类型
ClusterlP(Headless):默认值,它是Kubernetes系统自动分配的虚拟IP,缺点是只能在集群内部访问
NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务
ExternalName:把集群外部的服务引入集群内部,直接使用。此类Service用来引用一个已经存在的域名,CoreDNS会为该Service创建一个CName记录指向目标域名。
Headless:无头服务,是用户在Spec显示指定ClusterIP为None的Service,对于这类Service,API Server不会为其分配ClusterIP。CoreDNS为此类Service创建多条A记录,并且目标为每个就绪的PodIP。 另外,每个Pod会拥有一个FQDN格式为$podname.$svcname.$namespace.svc.$clusterdomain
的A记录指向PodIP
证书管理和七层负载均衡的需求
需要gRPC负载均衡如何做?
DNS需求
与上下游服务的关系
服务发布的挑战
kube-dns
Service
Ingress
服务发现
集中式LB服务发现
进程内LB服务发现
独立LB进程服务发现
系统的扩展可分为纵向(垂直)扩展和横向(水平)扩展。
负载均衡的作用(解决的问题):
早期以前,在DNS服务器,配置多个A记录,这些A记录对应的服务器构成集群
网络地址转换
网络地址转换(Network Address Translation,NAT)通常通过修改数据包的源地址(Source NAT)或目标地址(Destination NAT)来控制数据包的转发行为。
链路层负载均衡
隧道技术
负载均衡中常用的隧道技术是IPover IP,其原理是保持原始数据包IP头不变,在IP头外层增加额外的IP包头后转发给上游服务器。
上游服务器接收IP数据包,解开外层IP包头后,剩下的是原始数据包。
同样的,原始数据包中的目标IP地址要配置在上游服务器中,上游服务器处理完数据请求以后,响应包通过网关直接返回给客户端。
Service Selector
Ports:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: kube-system
spec:
ports:
- name: nginx
port: 80
protocol: TCP
targetPort: 80 (宿主机端口)
selector:
app: nginx
(Endpoint理解为中间表,描述的Service和Pod之间的地址映射关系,与Service同名。ep会把pod的IP填充到subset属性里)
EndpointSlice对象
不定义Selector的Service
用户创建了Service但不定义Selector(场景:用于指定外部虚拟机)
通过该类型服务,可以为集群外的一组Endpoint创建服务
headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
spec:
ClusterIP: None
ports:
- port: 80
protocol: TCP
name: http
selector:
app: nginx
externalName.yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: tencent.com
Service Topology
Service在很多情况下只是一个概念,真正起作用的其实是 kube-proxy 服务进程,每个Node节点上都运行着一个 kube-proxy 服务进程。
当创建Service的时候会通过api-server向etcd写入创建的service的信息,而kube-proxy会基于监听的机制发现这种service的变动,然后它会将最新的service信息转换成对应的访问规则(iptables规则或ipvs规则)。
每台机器上都运行一个kube-proxy服务,它监听API server中service和endpoint的变化情况,并通过iptables等来为服务配置负载均衡(仅支持TCP和UDP)。
kube-proxy可以直接运行在物理机上,也可以以static pod或者Daemonset的方式运行。
kube-proxy当前支持以下几种实现
从k8s的1.8版本开始,kube-proxy引入了IPVS模式,IPVS模式与iptables同样基于Netfilter,但是ipvs采用的hash表,iptables采用一条条的规则列表。iptables又是为了防火墙设计的,集群数量越多iptables规则就越多,而iptables规则是从上到下匹配,所以效率就越是低下。因此当service数量达到一定规模时,hash查表的速度优势就会显现出来,从而提高service的服务性能
每个节点的kube-proxy负责监听API server中service和endpoint的变化情况。将变化信息写入本地userspace、iptables、ipvs来实现service负载均衡,使用NAT将vip流量转至endpoint中。由于userspace模式因为可靠性和性能(频繁切换内核/用户空间)早已经淘汰,所有的客户端请求svc,先经过iptables,然后再经过kube-proxy到pod,所以性能很差。
ipvs和iptables都是基于netfilter的,两者差别如下:
切换操作
1、开启内核操作
cat >>/etc/sysct.conf <-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
EOF sysctl-p
2、开启ipvs支持
yum-y install ipvsadm ipset
#临时生效
modprobe-ip_vs
modprobe-ip_vs_rr
modprobe--ip_vs_wrr
modprobe--ip_vs_sh
modprobe-nf_conntrack_ipv4
#永久生效
cat>/etc/sysconfig/modules/pvs.modules <--ip_vs
modprobe--ip_vs_rr
modprobe--ip_vs_wrr
modprobe-ip_vs_sh
modprobe-nf_conntrack_ipv4
EOF
3、配置kube-proxy
vim /usr/lib/systemd/system/kube-proxy.service
在ExecStart后面加上:
# 添加下⾯两⾏
--proxy-mode=ipvs \
--masquerade-all=true
4、重启kube-proxy
systemctl daemon-reload
systemctl restart kube-proxy
systemctl status kube-proxy
CoreDNS
CoreDNS包含一个内存态DNS,以及与其他controller类似的控制器。
CoreDNS的实现原理是,控制器监听Service和Endpoint的变化并配置DNS,客户端Pod在进行域名解析时,从CoreDNS中查询服务对应的地址记录(service_name.namespace_name.svc.cluster_name(默认是cluster.local)
,把FQDN指向svc的cluster_ip,把映射的A记录存到CoreDNS里)。
k8s中的域名解析
$cat/etc/resolv.conf
search ns1.svc.cluster.local svc.cluster.local cluster.local #(默认加在svc-name的后面)
nameserver 192.168.0.10 #(这个DNS的IP地址为kube-dns的Cluster-IP)
options ndots:4
Ingress
Ingress Controller
service本身就是一个四层的负载策略,针对外部服务类型:nodeport或loadbalance
deployment与svc
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-dm
spec:
replicas: 2
template:
metadata:
labels:
name: nginx
spec:
containers:
- name: nginx
image: wangyanglinux/myapp:v1
imagePullPolicy: IfNotPresent # 如果有就不下载
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
selector: # 匹配,当name=nginx的时
name: nginx
ingress
[root@k8s-master01 ~]# vim ingress1.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-test
spec:
rules:
- host: www1.atguigu.com
http:
paths:
- path: /
backend:
serviceName: nginx-svc # 链接的是上面svc的名字
servicePort: 80
另外一个例子:https://github.com/cncamp/101/tree/master/module8/ingress
其中的Generate key-cert需要进行cat tls.crt | base64 -w 0,然后再放进去