容器化入门(五)--k8s进阶

文摘   2024-07-21 20:42   英国  

容器化入门(五)--k8s进阶

本文内容主要来源于极客时间《Kubernetes 入门实战课》,夹杂了自己在学习过程中的思考和相关记录

1.为什么需要Deployment:

  • 我们除了一次性的这种离线业务,还有很多是在线业务,要求实时在线。最简单的Pod具备监控内部容器状态、异常时自动重启容器的能力,但是在Pod本身被误删、Pod所在节点宕机离线等场景下,还是无法保证可靠性。
  • 在线应用还应当具备多实例、平滑扩容、版本升级回滚等特性,Pod没有这些能力

解决这些问题办法也很简单,参考k8s惯用的“单一职责”和“对象组合”的风格,我们要再创建一个新的对象类型,它来管理Pod,并且它自己具备可靠性、升级回滚、平滑扩容等能力。

2.Deployment的YAML参考:还是可以使用之前的方法获取一份YAML模板(deploy是Deployment的简写)

export out="--dry-run=client -o yaml"
kubectl create deploy ngx-dep --image=nginx:alpine $out

YAML模板如下,可以发现,它里面是嵌套了Pod的定义(参考spec.template

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: ngx-dep
  name: ngx-dep
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ngx-dep
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: ngx-dep
    spec:
      containers:
      - image: nginx:alpine
        name: nginx
        resources: {}
status: {}

3.Deployment的关键属性解析:

  • replicas:副本数量,指明要运行多少个Pod实例,k8s会持续监视Pod运行状态,如果因为各种意外导致Pod数量不满足目标状态,它就会通过apiserer、schedule等组件选择新的节点、创建新的Pod直至数量与期望状态一致
  • replicas:选择器、筛选出要被Deployment管理的Pod对象,下属字段matchLabels定义了要被管理的Pod应当携带的label,它必须和template中定义的Pod的labels对应上,否则Deployment就找不到要控制的Pod对象,apiserver也会在创建的时候进行校验。这里这种贴标签的方式其实体现的是Deployment和Pod的这种松耦合关系,方便其他的对象再去管理这些Pod,也方便这些Pod有需要时可以脱离原Deployment的管理(灰度发布等场景下,老版本的Pod逐步下线) 下图展示了Deployment对象定义中的关键信息:

4.Deployment的相关操作

  • 根据yaml创建对象:
kubectl apply -f ngx-dep.yaml
  • 查询状态:
kubectl get deployment
kubectl get deploy

其中:READY指的是运行的POD数量;UP-TO-DATE指的是已经更新到最新状态的POD数量;AVAILABLE更近一步、表示状态健康能对外提供服务的POD数量;AGE代表Deployment创建到现在所运行的时间

  • 查询被管理的Pod:
kubectl get pod
  • 模拟Pod被异常删除的场景,发现新Pod被自动拉起
  • 应用伸缩:可以动态调整副本数kubectl scale --replicas=5 deploy ngx-dep

注意,scale只能临时调整,如果系统重启Deployment重新根据yaml拉起后副本数还是yaml中描述的,所以如果想要长期生效,最好还是改yaml、再通过applay命令生效

  • label字段的进阶用法:可以在kubeclt getkubectl delete等命令中通过-l参数根据标签来筛选对象,支持==!=innotin等表达式
kubectl get pod -l 'app in (nginx)'
kubectl get pod -l 'app in (nginx,ngx-dep,ngx)'

5.为什么需要有Daemonset?Deployment解决了应用可靠性、平滑扩容等问题,但是它不关注Pod到底运行在哪些节点上,只保证Pod数量够;有一类业务不是完全独立与系统运行的,需要与主机存在绑定关系才有意义,需要保证每个节点一个,例如网络应用、节点监控应用、节点安全应用等。Daemonset的目标是每个节点运行且只运行一个Pod

6.Daemonset的YAML模板:当前kubectl不支持通过kubectl create创建Daemonset对象,所以没法再用之前的方法创建模板。

可以在官网下载一份YAML示例(另外,其实Daemonset和Deployemnt的YAML高度相似,可以从Deployemnt简单修改就可以得到Daemonset的YAML)

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: redis-ds
  labels:
    app: redis-ds

spec:
  selector:
    matchLabels:
      name: redis-ds

  template:
    metadata:
      labels:
        name: redis-ds
    spec:
      containers:
      - image: redis:5-alpine
        name: redis
        ports:
        - containerPort: 6379

跟Deployemnt的YAML相比,只是少了一个repelicas字段以及一些对象基础信息变了而已,其他完全一致

7.污点和容忍度:

  • 用途:为了方便在节点上调度Pod,k8s引入了污点的概念,它的作用是给节点贴标签。给节点打了标签后,就可以给Pod定义筛选机制、能让k8s在调度Pod时做一些控制调整,例如某个Pod因为某些原因不能运行到某节点上,这个筛选机制就是Pod的容忍度。如果Pod无法容忍某节点的污点,则该Pod无法调度到该节点上

  • 查看节点污点Taints:

kubectl describe node master
  • 减少一个污点,注意最后的减号
kubectl taint node master node-role.kubernetes.io/master:NoSchedule-
  • 增加一个污点
kubectl taint node master node-role.kubernetes.io/master:NoSchedule-
  • 修改Pod的容忍度,让它能容忍一些污点.要写清楚污点的名称和效果,同时写清楚如何匹配污点(Exists)。Pod默认不会容忍任何污点
tolerations:
- key: node-role.kubernetes.io/master
  effect: NoSchedule
  operator: Exists
  • 在k8s中,master一般具备一个node-role.kubernetes.io/master污点,为了使得ds能在mater上也能调度一个pod,可以在pod的定义中写清楚Pod的容忍度。当然,你也可以修改master节点的污点,但是这会导致其他Pod调度也会出现调整、将变更扩大了

8.使用DaemonSet:

  • 根据yaml创建DaemonSet
kubectl apply -f redis-ds.yaml
  • 查询状态:
kubectl get ds

9.静态Pod:DaemonSet 是在 Kubernetes 里运行节点专属 Pod 最常用的方式,但它不是唯一的方式,Kubernetes 还支持另外一种叫“静态 Pod”的应用部署手段,直接在节点/etc/kubernetes/manifests 目录下定义对应的Pod即可,静态Pod非常特殊、不受k8s管理:

10.Service:Service用来解决服务发现的问题,所谓服务发现就是如果让系统内部动态稳定(例如,Pod的个数稳定、但是Pod因为某些原因发生了漂移、它的IP随之变化)的服务对外提供稳定的服务,针对这种场景典型的解决思路就是负载均衡,在前端和后端之间加入一个中间层,屏蔽后端的变化,为前端提供一个稳定的服务。Service这种api资源就是k8s的负载均衡,用来解决服务发现这一关键问题。k8s中的Service可以由iptables技术实现,但是也可以由userspace(性能更差)和ipvs(性能更好)技术实现

11.Service的YAML描述:

  • 还是可以使用之前的技巧,使用--dry-run=client -o yaml生成Service对象的YAML模板,只不过要使用kubectl expose指令,同时要注意的是,kubectl expose指令支持给多种对象创建服务,Pod、Deployment、DaemonSet都可以。同时要通过--port--target-port分别指定映射端口和容器端口。
export out="--dry-run=client -o yaml"
kubectl expose deploy ngx-dep --port=80 --target-port=80 $out
  • 可以发现Service的YAML非常简单。在spec中只有两个关键字段,selector用于选择要代理的Pod,ports用于表示Service对外体现的端口、内部Pod的端口以及使用的协议
apiVersion: v1
kind: Service
metadata:
  name: ngx-svc
  
spec:
  selector:
    app: ngx-dep
    
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP

12.在k8s中使用Service:

  • 改造下ngx-dep,使得接口请求时能返回主机名等信息。我们将nginx的配置文件使用ConfigMap来管理:
apiVersion: v1
kind: ConfigMap
metadata:
  name: ngx-conf

data:
  default.conf: |
    server {
      listen 80;
      location / {
        default_type text/plain;
        return 200
          'srv : $server_addr:$server_port\nhost: $hostname\nuri : $request_method $host $request_uri\ndate: $time_iso8601\n';
      }
    }
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ngx-dep

spec:
  replicas: 2
  selector:
    matchLabels:
      app: ngx-dep

  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      volumes:
      - name: ngx-conf-vol
        configMap:
          name: ngx-conf

      containers:
      - image: nginx:alpine
        name: nginx
        ports:
        - containerPort: 80

        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: ngx-conf-vol
  • 部署ConfigMap、Deployment以及Service对象:
kubectl apply  -f ngx-conf-cm.yaml 
kubectl apply -f ngx-dep-ex.yaml 
kubectl apply -f ngx-svc.yaml

可以查询他们的状态:

可以查看svc代理了哪些后台的ip地址,并确认跟deployment创建的Pod ip地址一致

kubectl describe svc ngx-dep
  • 测试svc的负载均衡效果:因为Service、Pod的IP地址都是k8s集群的内部地址,我们需要kube exec进入到Pod内部或者ssh登录集群节点,用curl来访问svc的ip:
  • 我们可以删除一个Pod,看看deploy创建出新的Pod后,svc对象代理的后端IP会不会刷新。经过确认,svc自动刷新了后端代理的Pod的IP地址,但是svc对外的IP保持不变,这样就实现了对外提供稳定的服务。
  • 需要注意,Service的IP只是一个虚拟IP, 只用来转发流量,所以是无法ping通的

13.使用域名的方式来使用Service:

  • 为什么需要:Service对象的IP地址是静态的,保持稳定,可以使用k8s的DNS插件,为Service创建容易使用的域名
  • 名字空间namespce:k8s中的namespace用于实现对API对象的隔离和分组。可以使用kubectl get ns获取。k8s有一个默认的命名空间default,如果没有显示指定,API对象会在这个namespace中。为了避免冲突,名字空间是域名的一部分
  • Service对象的域名是对象.名字空间.svc.cluster.local.但很多时候,可以简写为对象.名字空间甚至对象
  • Kubernetes 也为每个 Pod 分配了域名,形式是“IP 地址. 名字空间.pod.cluster.local”,但需要把 IP 地址里的 . 改成 - 。比如地址 10.10.1.87,它对应的域名就是 10-10-1-87.default.pod

14.如何让Service对外暴露服务

  • 前面看到我们使用Service还不能真正提供服务、它最终对外的IP地址是一个集群内部的IP地址, 这是因为我们使用的负载均衡类型是“ClusterIP”,这种类型的负载均衡的IP只能在集群内部访问
  • Service实际总共支持四种类型:“ClusterIP”是默认的一种,其他三种分别是“ExternalName”、“LoadBalancer”和“NodePort”,“ExternalName”、“LoadBalancer”主要由云服务商提供
  • 如果我们在kubectl expose的时候加上--type=NodePort或者在YAML里添加字段type:NodePort后,Service除了对后端Pod做负载均衡之外会在每个节点上创建出一个独立的端口,用这个端口可以向外部提供服务
apiVersion: v1
...
spec:
  ...
  type: NodePort
  • 我们看一下效果。

直接访问集群节点的外部IP地址对应端口,即可访问服务

NodePort类型的Service示意图如下:NodePort类型Serive的缺点:

  • 端口数量有限,默认只在30000-32767
  • 每个节点都开端口,然后使用kube-proxy路由到真正的Service,对于很多计算节点大的集群来说带来了很多网络通信成本
  • 它要求向外界暴露节点的IP地址,很多时候因为安全原因还要再搭一个反向代理,增加了方案的复杂度

15.使用VirtualBox创建的虚拟机安装k8s过程中遇到的几个坑:

在本节遇到了几个非常奇怪的问题,折腾了比较久的时间,在此总结下:

问题1:kubectl exec时报错error: unable to upgrade connection: pod does not exist:

  • 问题原因:虚拟机的网络配置了两张网卡(一张是HostOnly、一张是NAT转化),针对第二张网卡,所有虚拟机的地址都是10.0.3.15/24。k8s管理节点时节点的IP都是10.0.3.15/24,可以从kubectl get nodes -o wide 看到两个节点IP都是10.0.3.15/24,这导致网络存在一点问题
  • 解决办法:在/etc/systemd/system/kubelet.service.d/10-kubeadm.conf配置文件中通过Environment="KUBELET_EXTRA_ARGS=--node-ip=<worker IP address>"显式指明节点IP地址,然后重启kubelet sudo systemctl stop kubelet; sudo systemctl daemon-reload; sudo systemctl start kubelet

问题2:kubectl exec进入节点使用域名访问服务时,域名无法解析。curl ngx-svc curl: (6) Could not resolve host: ngx-svc。根据网上定位指导,发现Pod内发送的dns请求根本无法到达dns pod

  • 问题原因:针对多网卡场景,使用flannel时需要指明网卡,否则可能导致一些诡异的网络问题
  • 解决办法:在对应的kube-flannel.yml通过参数指定网卡名。然后使用kubectl apply -f重新应用

16.为什么需要Ingress:

  • Service本质是一个四层负载均衡,仅能根据IP地址和端口做一些简单的组合和判断,在TCP/IP上转发流量;而我们的应用是跑在七层的HTTP/HTTPS上的, 想要更多的高级路由条件,如主机名、uri、请求头等
  • Service还有一个缺点,它比较适合集群内部的服务。如果想要把服务暴露到集群外只能用NodePort或者LoadBalancer,缺乏足够的灵活。这就导致一种很无奈的局面:我们的服务空有一身本领,却没有合适的机会大展拳脚
  • 需要引入一个七层的负载均衡对象,同时这个对象还要承担流量总入口的角色,统管南北向的所有流量,让外部客户安全、顺畅、便捷的访问内部服务。Ingress应用而生,Ingress单词的本意是入口

17.什么是Ingress Controller:

  • 对比Service来理解:Service本身只是一些iptables规则,真正配置这些规则、应用这些规则的是kube-proxy。Ingress本身也只是一份HTTP路由规则,真正要把这些规则实施运行起来需要另外一个组件,这就是Ingress COntroller。它能够读取、应用Ingress规则,处理和调度流量
  • Ingress Controller需要做的功能太复杂,并且与上层的业务强相关,所以k8s并没有提供标准的内置实现,把Ingress Controller的实现交给了社区。其中使用最广泛的是Nginx公司开发实现的Ingress Controller

18.什么是Ingress Class:

  • 随着Ingress的大量应用,发现了很多问题,例如Ingress规则太多导致Ingress Controller负载重、项目想引入多个不同的Ingress Controller
  • k8s又提出Ingress Class的概念,让它插在Ingress 和 Ingress Controller 中间,作为流量规则和控制器的协调人,解除了 Ingress 和 Ingress Controller 的强绑定关系。Kubernetes 用户可以转向管理 Ingress Class,用它来定义不同的业务逻辑分组,简化 Ingress 规则的复杂度

19.Ingress和Ingress Class的YAML描述文件:

  • 通过 kubectl api-resources获取到他们的基本信息,包括APIVERSION、KINDE等。
$ kubectl api-resources  | grep ingress
ingressclasses                                 networking.k8s.io/v1                   false        IngressClass
ingresses                         ing          networking.k8s.io/v1                   true         Ingress
  • 建议还是通过kubectl create来创建模板文件。它需要2个额外的参数:--class指定从属的Ingress Class对象;--rule指定路由规则,基本形式是URI=Service即特定的HTTP路径转发到Service对象。
export out="--dry-run=client -o yaml"
kubectl create ing ngx-ing --rule="ngx.test/=ngx-svc:80" --class=ngx-ink $out

获取到的yaml文件如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ngx-ing
spec:
  ingressClassName: ngx-ink
  rules:
  - host: ngx.text
    http:
      paths:
      - backend:
          service:
            name: ngx-svc
            port:
              number: 80
        path: /
        pathType: Exact

其中要重点关注的2个关键字段:ingressClassName:指明使用哪种Ingress Class;rule:路由规则,规则较为复杂,支持host、http path以及更近一步的路径匹配(可以是精确匹配Exact或者前缀匹配Prefix)

  • Ingress Class本身无实际功能,只是联系Ingress和Ingress Controller。它的定义非常简单,在spec中指明controller即可。这里我们使用Nginx开发的nginx.org/ingress-controller.
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: ngx-ink
spec:
  controller: nginx.org/ingress-controller

20.在k8s中使用Ingress/Ingress Class:

  • 准备对应的yaml, 可以将Ingress和Ingress Class 两个对象的定义放到一个描述文件中,使用---隔开即可
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ngx-ing
spec:
  ingressClassName: ngx-ink
  rules:
  - host: ngx.text
    http:
      paths:
      - backend:
          service:
            name: ngx-svc
            port:
              number: 80
        path: /
        pathType: Exact

---

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: ngx-ink
spec:
  controller: nginx.org/ingress-controller
  • 创建对象kubectl apply -f
  • 使用kubectl describe查看对应的信息

21.在k8s中使用Ingress Controller:

  • 安装Nginx Ingress Controller的基本工作,创建一个独立的命名空间、设置对应的账号和权限、创建一个cm和secret用来配置HTTP/HTTPS服务等。参考https://github.com/chronolaw/k8s_study/tree/master/ingress 进行安装

  • 部署Ingress Controller。我们选择Deployment方式部署,Nginx提供了参考示例。我们只需要做简单修改:改名字、改更精简的镜像、加上args参数-ingress-class=ngx-ink用于管理我们创建的class

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ngx-kic-dep
  namespace: nginx-ingress

spec:
  replicas: 1
  selector:
    matchLabels:
      app: ngx-kic-dep

  template:
    metadata:
      labels:
        app: ngx-kic-dep
     #annotations:
       #prometheus.io/scrape: "true"
       #prometheus.io/port: "9113"
       #prometheus.io/scheme: http
    spec:
      serviceAccountName: nginx-ingress
      containers:
      #- image: nginx/nginx-ingress:2.2.0
      - image: nginx/nginx-ingress:2.2-alpine
        imagePullPolicy: IfNotPresent
        name: nginx-ingress
        ports:
        - name: http
          containerPort: 80
        - name: https
          containerPort: 443
        - name: readiness-port
          containerPort: 8081
        - name: prometheus
          containerPort: 9113
        readinessProbe:
          httpGet:
            path: /nginx-ready
            port: readiness-port
          periodSeconds: 1
        securityContext:
          allowPrivilegeEscalation: true
          runAsUser: 101 #nginx
          capabilities:
            drop:
            - ALL
            add:
            - NET_BIND_SERVICE
        env:
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        args:
          - -ingress-class=ngx-ink
          - -health-status
          - -ready-status
          - -nginx-status

          - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
          - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
  • 部署 Deployment并查询状态
 kubectl apply -f ngx-kic-dep.yaml
 kubectl get deploy -n nginx-ingress
 kubectl get pod -n nginx-ingress
  • 从整个过程中, 梳理API对象之间的关系
  • 还差最后一步,Ingress Controller本质是一个Pod,要对外提供服务,第一种方法是直接使用端口转发
kubectl port-forward -n nginx-ingress ngx-kic-dep-8859b7b86-llqmm 8080:80 &
  • 第二种办法, 创建一个NodePort类型的Service
apiVersion: v1
kind: Service
metadata:
  name: ngx-kic-svc
  namespace: nginx-ingress
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: ngx-kic-dep
  type: NodePort


编程废柴
努力Coding