本篇文章从一个go编写的无状态demo,梳理下k8s比较常用的操作内容。
文章内容结构:
-
构建go编写的的docker镜像,包含一个healthZ健康检查的接口 (已更新)
-
编写deployment.yaml并部署(已更新)
-
更新deployment对象(已更新)
-
排障并回滚deployment对象(已更新)
-
简单缩放deployment对象(已更新)
-
metrics-server部署,HPA实战(已更新)
-
k8s内应用网络连接(ingress,headlessservice&service)(已更新)
-
存活,就绪probe详解(已更新)
-
安装prometheus,配置aggregator,monitor测试customer-metricserver(未完成)
一、构建go-web测试镜像(main函数包含一个healthZ接口)
Go代码目录
main.go(有一个healthZ的接口,跑在8888端口上)
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/healthZ",health)
fmt.Println("web run")
http.ListenAndServe(":8888",nil)
}
func health(w http.ResponseWriter,req *http.Request){
fmt.Fprintf(w,"Hello GO WEB\n")
}
Dockerfile
FROM golang:1.15-alpine3.13 AS builder
WORKDIR /build
RUN adduser -u 10001 -D app-runner
ENV GOPROXY https://goproxy.cn
COPY go.mod .
#COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -a -o webDemo .
FROM alpine:3.13 AS final
WORKDIR /app
COPY --from=builder /build/webDemo /app/
#COPY --from=builder /build/config /app/config
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER app-runner
ENTRYPOINT ["/app/webDemo"]
构建docker镜像后push到远程仓库(这里为了方便直接用dockerhub)
docker build -t ryetan/webdemo:v1 .
...
[+] Building 261.3s (18/18) FINISHED
...
docker push ryetan/webdemo:v1
上图 ryetan/webdemo:v1 这个docker镜像就是本次练习需要使用的镜像
本地运行下这个镜像测试下功能:
docker run -it -d -p 18888:8888 ryetan/webdemo:v1
curl 127.0.0.1:18888/healthZ
Hello GO WEB
二、编写deployment.yaml并部署到kubernetes
官网上deployment控制器图解
我们根据上面的图,使用自己构建的镜像定义deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-demo-deployment
labels:
app: web-demo
spec:
#ReplicaSet对象,约束pod数量
replicas: 3
selector:
matchLabels:
app: web-demo
template:
metadata:
labels:
app: web-demo
spec:
containers:
- name: web-demo
image: ryetan/webdemo:v1
ports:
- containerPort: 8888
参考官网字段说明:
-
创建名为 web-demo-deployment(由 .metadata.name 字段标明)的 Deployment。
-
该 Deployment 创建三个(由 replicas 字段标明)Pod 副本。
-
selector 字段定义 Deployment 如何查找要管理的 Pods。 在这里,你只需选择在 Pod 模板中定义的标签(app:web-demo)。 不过,更复杂的选择规则是也可能的,只要 Pod 模板本身满足所给规则即可。
-
说明: matchLabels 字段是 {key,value} 偶对的映射。在 matchLabels 映射中的单个 {key,value} 映射等效于 matchExpressions 中的一个元素,即其 key 字段是 “key”,operator 为 “In”,value 数组仅包含 “value”。在 matchLabels 和 matchExpressions 中给出的所有条件都必须满足才能匹配。
-
template 字段包含以下子字段:
-
-
Pod 被使用 labels 字段打上 app:web-demo 标签。
-
Pod 模板规约(即 .template.spec 字段)指示 Pods 运行一个web-demo 容器, 该容器运行版本为 v1 的 ryetan/webdemo Docker Hub镜像。
-
创建一个容器并使用 name 字段将其命名为 web-demo。
-
一些deployment相关的常用命令:
-
查看 Deployment 对象的状态变化 : kubectl rollout status
-
获取ReplicaSet:kubectl get rs
-
查看每个 Pod 自动生成的标签:kubectl get pods --show-labels
踩坑提醒:多个控制器不要控制同一种标签的pod,会有问题。
三、更新deployment对象(这里更新下镜像版本v2)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-demo-deployment
labels:
app: web-demo
spec:
#ReplicaSet对象,约束pod数量
replicas: 3
selector:
matchLabels:
app: web-demo
template:
metadata:
labels:
app: web-demo
spec:
containers:
- name: web-demo
image: ryetan/webdemo:v2
ports:
- containerPort: 8888
deployment更新的逻辑,整理自官方文档:
更新后,查看新创建的rs,以及被缩容的老rs
四、排障并回滚deployment
将一个不存在的镜像ryetan/webdemo:v99 用来更新template image字段,制造故障
排障示例:
通过event事件获取信息:
ryetan@ryetandeMacBook-Pro ~ % kubectl get event -n default
LAST SEEN TYPE REASON OBJECT MESSAGE
49m Normal Pulling pod/web-demo-deployment-5d9f78687f-5276z Pulling image "ryetan/webdemo:v99"
55m Warning Failed pod/web-demo-deployment-5d9f78687f-5276z Failed to pull image "ryetan/webdemo:v99": rpc error: code = Unknown desc = Error response from daemon: manifest for ryetan/webdemo:v99 not found: manifest unknown: manifest unknown
查看pod信息概览:
ryetan@ryetandeMacBook-Pro ~ % kubectl get pods
NAME READY STATUS RESTARTS AGE
web-demo-deployment-5d9f78687f-5276z 0/1 ImagePullBackOff 0 46h
web-demo-deployment-7b76f44f95-fgjhj 1/1 Running 0 47h
web-demo-deployment-7b76f44f95-hsx4v 1/1 Running 0 47h
web-demo-deployment-7b76f44f95-qlrjv 1/1 Running 0 47h
其它常用命令:
kubectl get rs
kubectl get deployment
kubectl describe deployment/web-demo-deployment
回滚相关知识:
查看老的发布版本,发现只有版本号没有commit message:
一般情况下,为了记录,也为了可能存在的排障需求,在命令后追加 --record
,如下图:
回滚命令比较简单如下:
#回退上个版本
kubectl rollout undo deployment/web-demo-deployment
#如果回退到指定版本
--to-revision=4
五、简单缩放deployment对象
简单使用scale缩放下,看下效果:
kubectl scale deployment/web-demo-deployment --replicas=5
kubectl scale deployment/web-demo-deployment --replicas=2
六、metricserver部署,实战HPA
HPA功能使用步骤:
-
检查aggregator是否开启
-
开启metrics-server
-
配置deployment下资源对象(pod)的request,声明一个针对deployment的HPA对象
先说aggregator和metric-server以及hpa的关系,看图:
aggregator就是个proxy,放在kube-apiserver上面一层(不知道kube-apiserver的先去看看官方文档),metrics-server在aggregator下面一层,配置好了之后,能让外面通过api地址访问到,然后,HPA就能通过aggregator拿到metrics-server和自定义的custom metrics数据(比如prometheus,后面再说)
所以,我们按照一开始说的步骤来搞下:
如果你是使用 kubeadm 或者官方的 kube-up.sh 脚本部署 Kubernetes 集群的话,aggregator 模式就是默认开启的;
如果是手动 DIY 搭建的话,你就需要在 kube-apiserver 的启动参数里加上如下所示的配置,或者去master机器的/etc/manifests改静态pod的配置:
一个题外话:/etc/manifests这个地址的静态文件是如何给kubelet用的呢?
可以看到这个configmap是配置给kubelet的,于是找到manifests这个目录下相对应的apiserver配置,加上如下配置即可:
上面的知识涉及到静态pod,如果你是用kubeadm装的k8s,可以忽略这一步,不要看这段啰嗦的图文。
接着,部署metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
---
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
在部署的时候,大概率会遇到镜像拉不下来这个问题,围绕k8s会多次碰到镜像问题,解决思路也比较简单,去国内能找到镜像的地方找到,然后tag修改下:
我是从阿里获取的:
docker pull registry.cn-hangzhou.aliyuncs.com/xbazhen/metrics-server:v0.4.2
docker tag registry.cn-hangzhou.aliyuncs.com/xbazhen/metrics-server:v0.4.1 k8s.gcr.io/metrics-server:v0.4.2
也可以直接去改metrics-server-deployment.yaml,把image改了。
还有个错误tips:
使用以下方法解决:
修改metrics-server-deployment.yaml中镜像源k8s.gcr.io为
registry.cn-hangzhou.aliyuncs.com/google_containers
并添加修改以下参数
imagePullPolicy: IfNotPresent
args:
- --metric-resolution=30s
- --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP
- --kubelet-insecure-tls
# --metric-resolution=30s:从 kubelet 采集数据的周期;
# --kubelet-preferred-address-types:优先使用 InternalIP 来访问 kubelet,这样可以避免节点名称没有 DNS 解析记录时,通过节点名称调用节点 kubelet API 失败的情况(未配置时默认的情况);
# --kubelet-insecure-tls:kubelet 的10250端口使用的是https协议,连接需要验证tls证书。--kubelet-insecure-tls不验证客户端证书
# 将metrics-server-deployment.yaml文件中的镜像拉取策略修改为"IfNotPresent";
kubectl top nodes 命令测试下:
ryetan@ryetandeMacBook-Pro deployment % kubectl top nodesNAME CPU(cores) CPU% MEMORY(bytes) MEMORY% ryek8master01 70m 3% 1944Mi 52%
命令的方式声明一个HPA资源:
kubectl autoscale deployment/web-demo-deployment --min=3 --max=6 --cpu-percent=60
并不会生效,因为需要配置pod的request:
yaml里增加以下request的描述:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-demo-deployment
labels:
app: web-demo
spec:
#ReplicaSet对象,约束pod数量
replicas: 3
selector:
matchLabels:
app: web-demo
template:
metadata:
labels:
app: web-demo
spec:
containers:
- name: web-demo
image: ryetan/webdemo:v1
resources:
request:
cpu: "250m"
memory: "64Mi"
ports:
- containerPort: 8888
配置完之后观察下:
修改下hpa对象,再观察下:
可以看到自动缩容成功:
七、k8s内应用网络连接(ingress、service)
service官方文档:kubernetes.io/zh/docs/con…
service分为两种,普通的,以及无头的headless service,建议参考上面的文档详细理解
编写一个service的yaml
apiVersion: v1
kind: Service
metadata:
name: web-demo-service
spec:
selector:
app: web-demo
ports:
- name: web-demo-service-port
protocol: TCP
port: 80
#这里可以用端口号,用pod里面的name可以更加解耦#
targetPort: web-demo-port
#这里可以用端口号,用pod里面的name可以更加解耦#
查看下service的endpoint,以及测试直接访问pod和通过service访问
在pod里测试下dns service a记录
在pod里测试下dns pod a记录
编写headless service yaml
apiVersion: v1
kind: Service
metadata:
name: web-demo-service
spec:
clusterIP: None selector:
app: web-demo
ports:
- name: web-demo-service-port
protocol: TCP
port: 80
#这里可以用端口号,用pod里面的name可以更加解耦#
targetPort: web-demo-port
#这里可以用端口号,用pod里面的name可以更加解耦#
在pod里测试下dns service a记录
客户端通过Ingress访问POD的逻辑
Ingress 安装(不用新版本的准入控制)
部署Ingress yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
如果是bare-metal 安装,Ingress前面还要有一层service来代理所有的ingress对象,用nodeport暴露给公网
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
踩坑提醒
#ingress-nginx-controller的镜像,拉下来会很慢很慢,去阿里搞搞,老问题不多说了,大概命令如下
docker pull registry.cn-hangzhou.aliyuncs.com/mycs/nginx-ingress-controller:0.30.0
前面的搞好之后,创建Ingress对象,指向我们的web-demo-service,path就写pod里面的docker容器的webpath就好
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: web-demo-ingress
spec:
tls:
- hosts:
- webdemo.example.com
secretName: web-demo-secret
rules:
- host: webdemo.example.com
http:
paths:
- path: /healthZ
backend:
serviceName: web-demo-service
servicePort: 80
测试下是否正常:
#31978是上图 nodeport service的443映射端口
curl --resolve webdemo.example.com:31978:172.26.11.50 https://webdemo.example.com:31978/healthZ —insecure
Hello GO WEB
八、probe探针
livenessProbe,用cat命令来检查存活
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-demo-testprobe-deployment
labels:
app: web-demo-testprobe
spec:
#ReplicaSet对象,约束pod数量
replicas: 2
selector:
matchLabels:
app: web-demo-testprobe
template:
metadata:
labels:
app: web-demo-testprobe
spec:
containers:
- name: web-demo-testprobe
image: ryetan/webdemo:v2
args:
- /sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
ports:
- containerPort: 8888
name: web-demo-port
livenessProbe,用http请求来检查存活
先改下go-web的代码,让healthZ这个接口10s之后返回500
func main() {
started := time.Now()
http.HandleFunc("/healthZ", func(w http.ResponseWriter, req *http.Request) {
duration := time.Now().Sub(started)
if duration.Seconds() > 10 {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
} else {
w.WriteHeader(200)
w.Write([]byte("ok"))
}
})
fmt.Println("web run")
http.ListenAndServe(":8888",nil)
}
web-demo-httptestprobe.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-demo-testprobe-deployment
labels:
app: web-demo-testprobe
spec:
#ReplicaSet对象,约束pod数量
replicas: 2
selector:
matchLabels:
app: web-demo-testprobe
template:
metadata:
labels:
app: web-demo-testprobe
spec:
containers:
- name: web-demo-testprobe
image: ryetan/webdemo:v4
livenessProbe:
httpGet:
path: /healthZ
port: 8888
httpHeaders:
- name: Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
ports:
- containerPort: 8888
name: web-demo-port
还有一种是用tcp来测试,类似于telnet,以及就绪探针等其它知识抄抄下官方文档:
第三种类型的存活探测是使用 TCP 套接字。 通过配置,kubelet 会尝试在指定端口和容器建立套接字链接。 如果能建立连接,这个容器就被看作是健康的,如果不能则这个容器就被看作是有问题的。
pods/probe/tcp-liveness-readiness.yaml
apiVersion: v1
kind: Pod
metadata: name: goproxy
labels:
app: goproxy
spec: containers:
- name: goproxy
image: k8s.gcr.io/goproxy:0.1
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
如你所见,TCP 检测的配置和 HTTP 检测非常相似。 下面这个例子同时使用就绪和存活探测器。kubelet 会在容器启动 5 秒后发送第一个就绪探测。 这会尝试连接 goproxy 容器的 8080 端口。 如果探测成功,这个 Pod 会被标记为就绪状态,kubelet 将继续每隔 10 秒运行一次检测。
除了就绪探测,这个配置包括了一个存活探测。 kubelet 会在容器启动 15 秒后进行第一次存活探测。 就像就绪探测一样,会尝试连接 goproxy 容器的 8080 端口。 如果存活探测失败,这个容器会被重新启动。
failureThreshold: 30 * periodSeconds: 10 =300S
这两个参数组合起来最大300S是为了延迟检查的时间,防止重启,部署时在点火检查失败
Probe 有很多配置字段,可以使用这些字段精确的控制存活和就绪检测的行为:
-
initialDelaySeconds:容器启动后要等待多少秒后存活和就绪探测器才被初始化,默认是 0 秒,最小值是 0。
-
periodSeconds:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。
-
timeoutSeconds:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。
-
successThreshold:探测器在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。
-
failureThreshold:当探测失败时,Kubernetes 的重试次数。 存活探测情况下的放弃就意味着重新启动容器。 就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。
在 Kubernetes 1.20 版本之前,exec 探针会忽略 timeoutSeconds:探针会无限期地 持续运行,甚至可能超过所配置的限期,直到返回结果为止。
这一缺陷在 Kubernetes v1.20 版本中得到修复。你可能一直依赖于之前错误的探测行为, 甚至你都没有觉察到这一问题的存在,因为默认的超时值是 1 秒钟。 作为集群管理员,你可以在所有的 kubelet 上禁用 ExecProbeTimeout 特性门控 (将其设置为 false),从而恢复之前版本中的运行行为,之后当集群中所有的 exec 探针都设置了 timeoutSeconds 参数后,移除此标志重载。 如果你有 Pods 受到此默认 1 秒钟超时值的影响,你应该更新 Pod 对应的探针的 超时值,这样才能为最终去除该特性门控做好准备。
当此缺陷被修复之后,在使用 dockershim 容器运行时的 Kubernetes 1.20+ 版本中,对于 exec 探针而言,容器中的进程可能会因为超时值的设置保持持续运行, 即使探针返回了失败状态。