k8s_day03_02.md
Cloud Native 程序应该提供的接口 如下:
process heath:
以镜像格式打包,并托管于编排系统的容器引擎之上的容器,就是一个黑盒,因此我们想去探测一下容器内的应用、进程,健康与否都会被边界所阻挡,正常情况下,一个为云原生环境所开发的应用程序,他都该考虑到一个问题 ,为了便于监测容器运行的api, 或者为了监视容器是否运行健康的,正常情况下云原生应用应该将自身内部的指标向外输出, 比如 一定要有健康状态探测的接口。【我们只需要访问容器的某个指定位置,就能得到主容器主服务的健康状态。容器内通常运行的是一个服务进程,现在大多的分布式应用,多数都是以http/https 协议对外提供服务,基于/URI 可以提供特定的应用输出或者暴露接口,或者直接用端口 暴露应用状态以监控主应用 也行,可以理解为了使用2个virtualhost , 一个用于提供主服务,一个用于探测主服务是否正常的】 来输出应用的健康状态,将容器健康状态暴露给容器边界外部
metrics:
metrics 就是指标, 比如容器提供的是一个 web 服务 ,接收多少次请求,有多少个请求完成了,有多少请求丢弃了 ,占用了多少CPU,这些就可以称为指标数据。指标有业务级、系统级等等
readness: 就绪状态探测
liveness: 存活状态探测
tracing:
分布式链路追踪的埋点
,可以理解为就是探针。埋点提供了一个接口, 但针是什么时候下的你自己决定,它留了一个管
, readness,liveness,logs 亦如此
logs: 直接输出至控制台。直接向logs 接口发请求就行,为了能够通过api 获取,可能还需要其他的设定
pod 探针接口:
为了便于探测pod内容器运行健康与否,pod在设计上 直接在pod 级别 或者在pod内容器级别 就支持 允许用户去下探针的一个接口,这接口在pod之上,我们就称为pod探针接口
Pod 生命周期的3个探针:
LivenessProbe:
周期性检测,检测未通过时,kubelet会根据restartPolicy的定义来决定是否会重启该容器;未定义时,Kubelet认为只容器未终止,即为健康;
ReadinessProbe:
周期性检测,检测未通过时,与该Pod关联的Service,会将该Pod从Service的后端可用端点列表中删除;直接再次就绪,重新添加回来。未定义时,只要容器未终止,即为就绪; 如果由Ready 状态变为notReady , service 一定会把这个pod从后端摘除,而不是重启容器
如果livness没有定义,而只定义了readyness. 那么只要readyness 检测成功,就是ready状态
在生产环境中,每个应用都应该显示定义关键性pod就绪状态检测和健康状态检测
StartupProbe:
==只有当startup 探针正常运行结束退出后,定义的liveness和readness 才会生效。==如果这2个没定义,就使用它俩默认的定义的策略生效。传统模式开发出的很大应用,一般迁移至容器平台时,它启动时间就非常长,因此就应该使用启动状态探针进行检测,便于用户使用同livenessProbe不同参数或阈值; 如果没有startup 探针 ,那么readyness 和liveness 探针 就会从startup 探针所在位置开始执行, 意味着,那些应用启动时间长的 容器就会被周期健康检查,然后无限重启
三种探针的类型:
ExecAction:
直接执行命令,命令成功返回表示探测成功【命令成功返回表示,健康探针就表示健康、就绪探针就表示就绪】;
TCPSocketAction:
端口能正常打开,即成功; 【对于三次握手的第一次握手成功就行】
HTTPGetAction:
向指定的path发HTTP请求,2xx, 3xx的响应码表示成功; 4xx 5xx 不成功
每种探测类型都支持3种探针
探针的资源清单格式
spec:
containers:
- name: …
image: …
livenessProbe:
exec <Object> # 命令式探针
httpGet <Object> # http GET类型的探针
tcpSocket <Object> # tcp Socket类型的探针
initialDelaySeconds <integer> # 发起初次探测请求的延后时长
periodSeconds <integer> # 请求周期 ,默认10s,最小值为1
timeoutSeconds <integer> # 超时时长
successThreshold <integer> #成功阈值,发生状态改变后(连续探测成功时)的确认次数 为多少次才算成功 默认值1. liveness 、startup这2个状态必须设1
failureThreshold <integer> # 失败阈值 ,默认3 最小1
[root@master01 chapter4]# kubectl explain pods.spec.containers.livenessProbe.exec
注意: command 字段注意事项, 如果不加shell 提示符,可能不支持shell 的特殊字符如管道等
[root@master01 chapter4]# kubectl explain pods.spec.containers.livenessProbe.tcpSocket
主要指明2个字段 host、 port 。 指出向哪个地址端口发送请求就行了, 因为探针就在pod 上,探测就是pod内容器,一般使用127.0.0.1 的地址就ok 或者直接使用pod的地址也ok. 默认值就是pod ip ,不写也行
[root@master01 chapter4]# kubectl explain pods.spec.containers.livenessProbe.httpGet
同理,要指出 host(默认是pod ip)、port.
使用的协议scheme 是https 还是http (默认是http)
访问的路径 path 不指定就是根
额外的首部:httpHeaders (如果用到的话就写)
必选只有一个字段port
例子:
以liveness 状态探测 ,演示3种类型探针的用法
eg1 : exec命令探针
[root@master01 chapter4]# kubectl apply -f liveness-exec-demo.yaml
pod/liveness-exec-demo created
[root@master01 chapter4]# cat liveness-exec-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-demo
namespace: default
spec:
containers:
- name: demo
image: ikubernetes/demoapp:v1.0
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command: ['/bin/sh', '-c', '[ "$(curl -s 127.0.0.1/livez)" == "OK" ]']
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 5
[root@master01 chapter4]#
验证 describe 结果
Liveness: exec [/bin/sh -c [ "$(curl -s 127.0.0.1/livez)" == "OK" ]] delay=5s timeout=1s period=5s #success=1 #failure=3
发现success 和failure 使用的是默认值
这里的URI livez是自定义的,可以通过post 请求改变其响应结果
[root@node01 ~]# curl 10.244.5.239/livez
OK[root@node01 ~]#
[root@node01 ~]# curl -XPOST -d 'livez=fail' 10.244.5.239/livez
[root@node01 ~]# curl 10.244.5.239/livez
fail[root@node01 ~]#
describe po ,发现 events 说这个容器健康探测失败,会被重启只是重启pod内探测失败的容器,而不是整个pod
Normal Scheduled 8h default-scheduler Successfully assigned default/liveness-exec-demo to node01
Warning Unhealthy 82s (x6 over 2m57s) kubelet Liveness probe failed:
Normal Killing 82s (x2 over 2m47s) kubelet Container demo failed liveness probe, will be restarted
kubectl get po 命令的reday 状态1/1 的第一个1和pod内容器的就绪探针又关。后面那个1表示有pod内有多少个容器
[root@node01 ~]# kubectl get po/liveness-exec-demo NAME READY STATUS RESTARTS AGE liveness-exec-demo 1/1 Running 0 8h
eg2: tcp 探针
[root@master01 chapter4]# cat liveness-tcpsocket-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-tcpsocket-demo
namespace: default
spec:
containers:
- name: demo
image: ikubernetes/demoapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
securityContext:
capabilities:
add:
- NET_ADMIN
livenessProbe:
tcpSocket:
port: http
periodSeconds: 5
initialDelaySeconds: 5
注意这里的livenessProbe是引用了容器 port 的 name字段
对当创建的pod内容器 添加iptables 规则
[root@node01 ~]# kubectl exec liveness-tcpsocket-demo -- iptables -A INPUT -p tcp --dport 80 -j REJECT
查看结果events
Warning Unhealthy 2s (x3 over 12s) kubelet Liveness probe failed: dial tcp 10.244.4.15:80: i/o timeout
Normal Killing 2s kubelet Container demo failed liveness probe, will be restarted
eg3:
http 探针
[root@master01 chapter4]# cat liveness-httpget-demo.yaml
# Maintainer: MageEdu <mage@magedu.com>
# URL: http://www.magedu.com
# ---
apiVersion: v1
kind: Pod
metadata:
name: liveness-httpget-demo
namespace: default
spec:
containers:
- name: demo
image: ikubernetes/demoapp:v1.0
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: '/livez'
port: 80
scheme: HTTP
initialDelaySeconds: 5
整个容器内服务自定义的逻辑就是, 如果响应的内容不是ok ,那么响应码就是50x
[root@node01 ~]# curl -XPOST -d 'livez=fail' 10.244.5.241/livez
[root@node01 ~]# curl -I 10.244.5.241/livez
HTTP/1.0 506 VARIANT ALSO NEGOTIATES
Content-Type: text/html; charset=utf-8
Content-Length: 4
Server: Werkzeug/1.0.0 Python/3.8.2
Date: Sat, 04 Dec 2021 01:55:36 GMT
Liveness: http-get http://:80/livez delay=5s timeout=1s period=10s #success=1 #failure=3
x3 表示已经连续3次检测失败,21s 秒表示距离距离第一次检测过了21秒。又检测失败过2次 2x10(periodSeconds 默认值) .所以要重启
initialDelaySeconds的作用:推迟首次探测
如果readyness 没有定义, 那么容器一启动就默认为是ready的会被加入到service 的负载当中,用户就会访问就会莫名奇妙
[root@master01 chapter4]# cat readiness-httpget-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget-demo
namespace: default
spec:
containers:
- name: demo
image: ikubernetes/demoapp:v1.0
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
path: '/readyz'
port: 80
scheme: HTTP
initialDelaySeconds: 15
timeoutSeconds: 2
periodSeconds: 5
failureThreshold: 3
restartPolicy: Always
发现虽然是RUNNING 状态但是没有ready, 是因为初始的ready探测被延后了
[root@master01 chapter4]# kubectl get po/readiness-httpget-demo
NAME READY STATUS RESTARTS AGE
readiness-httpget-demo 0/1 Running 0 11s
钩子
start 和stop 钩子的执行方法和 探针的类型一样也是3种 exec tcpSocket httpGet
post start hook:
启动后钩子 在我们的容器创建完成后,立即运行钩子处理器,它的主要作用就是为容器启动后做初始化的
如初始化缓存空间,从互联网加载配置文件。 只有post start hook 成功了,才认为主容器会成功操作,pod才会显示running状态
容器启动后钩子 ,主容器进程和启动后钩子可能会同时启动,那个先启动完毕不一定,只不过在钩子执行完毕后pod才会被置于running状态。如果在主容器进程启动完毕之后之后钩子再执行完毕,那么启动后钩子就可能无法发挥响应的作用,这时后可能需要初始化容器来解决
pre stop hook:
清理操作,一般是主进程结束之前。
清理操作可能会有一些要求,因为只有清理完了,pod 才会真正终止。所以run pre stop 的时候,要确保pre stop 成功结束了,容器才能成功终止
eg:
人为模拟容器启动和终止前效果
[root@master01 chapter4]# kubectl apply -f lifecycle-demo.yaml
pod/lifecycle-demo created
[root@master01 chapter4]# cat lifecycle-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
namespace: default
spec:
containers:
- name: demo
image: ikubernetes/demoapp:v1.0
imagePullPolicy: IfNotPresent
securityContext:
capabilities:
add:
- NET_ADMIN
livenessProbe:
httpGet:
path: '/livez'
port: 80
scheme: HTTP
initialDelaySeconds: 5
lifecycle:
postStart:
exec:
command: ['/bin/sh','-c','iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-ports 80']
preStop:
exec:
command: ['/bin/sh','-c','while killall python3; do sleep 1; done']
restartPolicy: Always
验证结果:
当Pod处于Running 状态时, 启动前钩子命令已经执行完成
[root@master01 chapter4]# kubectl get po/lifecycle-demo
NAME READY STATUS RESTARTS AGE
lifecycle-demo 1/1 Running 0 66s
[root@master01 chapter4]# kubectl exec po/lifecycle-demo -- iptables -vnL -t nat
Chain PREROUTING (policy ACCEPT 11 packets, 660 bytes)
pkts bytes target prot opt in out source destination
0 0 REDIRECT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 redir ports 80
多容器pod
Sidecar
Sidecar为主容器提供辅助功能,随时和主容器同步运行, 所以叫 编车模式 ,比如如为主容器提供日志收集器、数据同步器、配置代理等等。或者代理服务也可以,给pod中每个容器都添加nginx代理服务器,也行。
在配置清单中的格式并无特殊之处,只不过是列表多一个项【1.18 版本有一个容器类型就叫sidecar 】
eg: 把sidecar 当作主容器的代理容器
在微服务当中,每一个应用通常要要求他的自身处理很多复杂的事情,比如说像限流、熔断、降级、分布式跟踪等功能,这些高级功能要想能用,必须得在主容器中写代码,调用分布式跟踪的sdk、监控系统的sdk、调用限流熔断的sdk在主容器理写代码, 如果有1000个微服务,这些都是重复性工作,现代的微服务使用了一种巧妙的方法:在服务网格当中,专门处理降级、追踪的功能放在一个应用当中实现【辅助容器,代理程序实现这类功能】,把业务代码放在一个应用中实现【主容器】
root@master01 chapter4]# cat sidecar-container-demo.yaml
# Maintainer: MageEdu <mage@magedu.com>
# URL: http://www.magedu.com
# ---
apiVersion: v1
kind: Pod
metadata:
name: root@master01 chapter4]# cat sidecar-container-demo.yaml
# Maintainer: MageEdu <mage@magedu.com>
# URL: http://www.magedu.com
# ---
apiVersion: v1
kind: Pod
metadata:
name: sidecar-container-demo
namespace: default
spec:
containers:
- name: proxy
image: envoyproxy/envoy-alpine:v1.14.1
command: ['/bin/sh','-c']
args: ['sleep 5 && envoy -c /etc/envoy/envoy.yaml']
lifecycle:
postStart:
exec:
command: ['/bin/sh','-c','wget -O /etc/envoy/envoy.yaml http://ilinux.io/envoy.yaml']
- name: demo
image: ikubernetes/demoapp:v1.0
imagePullPolicy: IfNotPresent
env:
- name: HOST
value: "127.0.0.1"
- name: PORT
value: "8080"
namespace: default
spec:
containers:
- name: proxy
image: envoyproxy/envoy-alpine:v1.14.1
command: ['/bin/sh','-c']
args: ['sleep 5 && envoy -c /etc/envoy/envoy.yaml']
lifecycle:
postStart:
exec:
command: ['/bin/sh','-c','wget -O /etc/envoy/envoy.yaml http://ilinux.io/envoy.yaml']
- name: demo
image: ikubernetes/demoapp:v1.0
imagePullPolicy: IfNotPresent
env:
- name: HOST
value: "127.0.0.1"
- name: PORT
value: "8080"
验证 sidecar 代理。sidecar 监听是80地址, 主容器是8080.
[root@master01 ~]# kubectl get po/sidecar-container-demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
sidecar-container-demo 2/2 Running 0 44s 10.244.5.14 node01 <none> <none>
[root@master01 ~]# curl -I 10.244.5.14
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 108
server: envoy
date: Sat, 04 Dec 2021 19:17:39 GMT
x-envoy-upstream-service-time: 2
Adapter
为主容器的某个能适配到外部环境而设定的, 而不是为了接收请求,或者辅助主容器干嘛别的活,是为了主容器响应能更好的适配到外部环境而设定的 。比如默认的nginx status 输出的内容格式不兼容普罗米修斯, 可以添加设置一个nginx_exporter_adaptor ,适配器容器能够自己请求nginx容器中的status参数 结果,并且把它转为支持普罗米修斯格式的内容再输出出去
Ambassdor
大使模式是为了让容器接入到
外部多变的环境而设定的 。 sidecar 是为了让外 更能接入pod内的主容器设定的。而Ambassdor是 向外的
, 最典型场景是代表主容器访问数据库。 比如主容器 要不产生的数据放在远程的数据库,
而外面的数据可能是 mysql 、oracle 各种类型的, 这时候,主容器不管数据如何存 ,把它交给大使容器,由他代为完成
init container
初始化容器可能有多个 ,串行执行 ,在主容器启动时退出 ,如果主容器还有辅助容器sidecar,那么主容器和辅助容器的启动先后次序是不确定的,但是会一起结束.
用于特权操作。为了让主容器初始化操作,可以修改iptables ,在pod内就不得不让主容器拥有netadmin的能力,就意味这不仅钩子的iptables命令可以操作内核,还有其他命令也可以操作网络部分内核功能。 这就是权限的额外影响。可以把netadmin的能力附加给初始化容器 ,它执行完了 就对整个pod内的网络名称空间就已经生效了,然后它就退出了,这样pod内所有容器都不具有netadmin能力,因为pod内容器共享网络名称空间,所以主容器的网络规则也会生效,这样就不用给主容器netadmin 权限了。
把主容器的特权操作放在初始化容器的好处就是免得主容器一直拥有特殊权限
[root@master01 chapter4]# kubectl apply -f init-container-demo.yaml
pod/init-container-demo created
[root@master01 chapter4]# cat init-container-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: init-container-demo
namespace: default
spec:
initContainers:
- name: iptables-init
image: ikubernetes/admin-box:latest
imagePullPolicy: IfNotPresent
command: ['/bin/sh','-c']
args: ['iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 80']
securityContext:
capabilities:
add:
- NET_ADMIN
containers:
- name: demo
image: ikubernetes/demoapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
验证结果: 虽然主容器没有开启iptables 端口转发,但是可以却可以访问8080, 而且iptables 命令无权限使用
[root@node01 ~]# kubectl get po/init-container-demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
init-container-demo 1/1 Running 0 2m27s 10.244.4.17 node02 <none> <none>
[root@node01 ~]# curl 10.244.4.17:8080
iKubernetes demoapp v1.0 !! ClientIP: 10.244.5.0, ServerName: init-container-demo, ServerIP: 10.244.4.17!
[root@node01 ~]# curl 10.244.4.17:80
iKubernetes demoapp v1.0 !! ClientIP: 10.244.5.0, ServerName: init-container-demo, ServerIP: 10.244.4.17!