kubernetes运维操作手册(持续更新)

689 阅读12分钟

本篇文章从一个go编写的无状态demo,梳理下k8s比较常用的操作内容。

文章内容结构:

  1. 构建go编写的的docker镜像,包含一个healthZ健康检查的接口 (已更新)

  2. 编写deployment.yaml并部署(已更新)

  3. 更新deployment对象(已更新)

  4. 排障并回滚deployment对象(已更新)

  5. 简单缩放deployment对象(已更新)

  6. metrics-server部署,HPA实战(已更新)

  7. k8s内应用网络连接(ingress,headlessservice&service)(已更新)

  8. 存活,就绪probe详解(已更新)

  9. 安装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

官方文档:kubernetes.io/zh/docs/con…

官网上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功能使用步骤:

  1. 检查aggregator是否开启

  2. 开启metrics-server

  3. 配置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

文档:github.com/kubernetes-…

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 安装(不用新版本的准入控制)

文档地址:github.com/kubernetes/…

部署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 探针而言,容器中的进程可能会因为超时值的设置保持持续运行, 即使探针返回了失败状态。