Kubernetes 实战系列-2:核心概念

什么是 Kubernetes

Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态,其服务、支持和工具的使用范围相当广泛。

Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”。k8s 这个缩写是因为 k 和 s 之间有八个字符的关系。 Google 在 2014 年开源了 Kubernetes 项目。 Kubernetes 建立在 Google 大规模运行生产工作负载十几年经验的基础上, 结合了社区中最优秀的想法和实践。

image-20240220142127511

Kubernetes 提供了一个可弹性运行分布式系统的框架,它可以为你提供:

  • 服务发现和负载均衡

    Kubernetes 可以使用 DNS 名称或自己的 IP 地址来暴露容器。 如果进入容器的流量很大, Kubernetes 可以负载均衡并分配网络流量,从而使部署稳定。

  • 存储编排

    Kubernetes 允许你自动挂载你选择的存储系统,例如本地存储、公共云提供商等。

  • 自动部署和回滚

    你可以使用 Kubernetes 描述已部署容器的所需状态, 它可以以受控的速率将实际状态更改为期望状态。 例如,你可以自动化 Kubernetes 来为你的部署创建新容器, 删除现有容器并将它们的所有资源用于新容器。

  • 自动完成装箱计算

    你为 Kubernetes 提供许多节点组成的集群,在这个集群上运行容器化的任务。 你告诉 Kubernetes 每个容器需要多少 CPU 和内存 (RAM)。 Kubernetes 可以将这些容器按实际情况调度到你的节点上,以最佳方式利用你的资源。

  • 自我修复

    Kubernetes 将重新启动失败的容器、替换容器、杀死不响应用户定义的运行状况检查的容器, 并且在准备好服务之前不将其通告给客户端。

  • 密钥与配置管理

    Kubernetes 允许你存储和管理敏感信息,例如密码、OAuth 令牌和 SSH 密钥。 你可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置,也无需在堆栈配置中暴露密钥。

  • 批处理执行

    除了服务外,Kubernetes 还可以管理你的批处理和 CI(持续集成)工作负载,如有需要,可以替换失败的容器。

  • 水平扩缩

    使用简单的命令、用户界面或根据 CPU 使用率自动对你的应用进行扩缩。

    ......

工作方式

Kubernetes Cluster = N Master Node + N Worker Node = N 个主节点 + N 个工作节点(N >= 1)

组件架构

image-20240220142153822

控制平面组件(Control Plane Components)

控制平面组件会为集群做出全局决策,比如资源的调度。 以及检测和响应集群事件(例如:当不满足部署的 replicas 字段时,要启动新的 Pod)。

控制平面组件可以在集群中的任何节点上运行。 然而,为了简单起见,设置脚本通常会在同一个计算机上启动所有控制平面组件, 并且不会在此计算机上运行用户容器。 请参阅 使用 kubeadm 构建高可用性集群 中关于跨多机器控制平面设置的示例。

kube-apiserver

API 服务器是 Kubernetes 控制平面 的组件, 该组件负责公开了 Kubernetes API,负责处理接受请求的工作。 API 服务器是 Kubernetes 控制平面的前端。

Kubernetes API 服务器的主要实现是 kube-apiserverkube-apiserver 设计上考虑了水平扩缩,也就是说,它可通过部署多个实例来进行扩缩。 你可以运行 kube-apiserver 的多个实例,并在这些实例之间平衡流量。

etcd

ectd 是兼具一致性且高可用的键值数据库,用作 Kubernetes 所有集群数据的后台数据库。

如果你的 Kubernetes 集群使用 etcd 作为其后台数据库, 请确保你针对这些数据有一份 备份 计划。

你可以在 官方文档 中找到有关 etcd 的深入知识。

kube-scheduler

kube-scheduler控制平面的组件, 负责监视新创建的、未指定运行节点(node)Pods, 并选择节点来让 Pod 在上面运行。

调度决策考虑的因素包括单个 Pod 及 Pods 集合的资源需求、软硬件及策略约束、 亲和性及反亲和性规范、数据位置、工作负载间的干扰及最后时限。

kube-controller-manager

kube-controller-manager控制平面的组件, 负责运行控制器进程。

从逻辑上讲, 每个控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在同一个进程中运行。

有许多不同类型的控制器。以下是一些例子:

  • 节点控制器(Node Controller):负责在节点出现故障时进行通知和响应

  • 任务控制器(Job Controller):监测代表一次性任务的 Job 对象,然后创建 Pod 来运行这些任务直至完成

  • 端点分片控制器(EndpointSlice controller):填充端点分片(EndpointSlice)对象(以提供 Service 和 Pod 之间的链接)。

  • 服务账号控制器(ServiceAccount controller):为新的命名空间创建默认的服务账号(ServiceAccount)。

以上并不是一个详尽的列表。

cloud-controller-manager

一个 Kubernetes 控制平面组件, 嵌入了特定于云平台的控制逻辑。 云控制器管理器(Cloud Controller Manager) 允许将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。

cloud-controller-manager 仅运行特定于云平台的控制器。 因此如果你在自己的环境中运行 Kubernetes,或者在本地计算机中运行学习环境, 所部署的集群不需要有云控制器管理器。

kube-controller-manager 类似,cloud-controller-manager 将若干逻辑上独立的控制回路组合到同一个可执行文件中, 供你以同一进程的方式运行。 你可以对其执行水平扩容(运行不止一个副本)以提升性能或者增强容错能力。

下面的控制器都包含对云平台驱动的依赖:

  • 节点控制器(Node Controller):用于在节点终止响应后检查云提供商以确定节点是否已被删除

  • 路由控制器(Route Controller):用于在底层云基础架构中设置路由

  • 服务控制器(Service Controller):用于创建、更新和删除云提供商负载均衡器

Node 组件

节点组件会在每个节点上运行,负责维护运行的 Pod 并提供 Kubernetes 运行环境。

kubelet

kubelet 会在集群中每个节点(node)上运行。 它保证容器(containers)都运行在 Pod 中。

kubelet 接收一组通过各类机制提供给它的 PodSpec,确保这些 PodSpec 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器。

kube-proxy

kube-proxy 是集群中每个节点(node)上所运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。

kube-proxy 维护节点上的一些网络规则, 这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。

如果操作系统提供了可用的数据包过滤层,则 kube-proxy 会通过它来实现网络规则。 否则,kube-proxy 仅做流量转发。

资源创建方式

  • 命令行

    通过使用 kubectl 命令的方式手动创建资源,适合初学者学习使用,好处是容易上手,不用编写 yaml 文件即可操作各种资源,坏处是必须记住命令,且不利于重复化使用。

  • YAML 文件

    通过编写符合 Kubernetes 规范的 YAML 文件来组织资源的创建方式,好处是规范、持久化、可重复利用,能够让部署可以像代码一样管理,坏处是需要熟悉配置文件的语法才能使用此方法。

Namespace

Namespace 表示命名空间,用来隔离资源。类似于 Nacos 中的 namespace。

namespace 的 short-name 为 ns

# 命名空间的名称需要唯一
kubectl create ns hello
kubectl delete ns hello
apiVersion: v1
kind: Namespace
metadata:
  name: hello

Pod

Pod 是 Kubernetes 中调度和运行的最小单元,其内部可以有一至多个容器实例,相当于在 Docker 的 Container 上封装了一层,变成了 Pod。

image-20240205164352421

pod 的 short-name 为 po

# 运行一个nginx Pod
kubectl run mynginx --image=nginx
​
# 查看default名称空间的Pod
kubectl get pod
​
# 描述
kubectl describe pod <PodName>
# 删除
kubectl delete pod <PodName>
# 查看Pod的运行日志
kubectl logs <PodName>
​
# 每个Pod都会被分配一个ip
kubectl get pod -owide
# 使用Pod的ip+port访问运行容器的端口
curl 192.168.10.101:80
​
# 集群中的任意一个机器以及任意的应用都能通过Pod分配的ip来访问这个Pod
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: mynginx
  name: mynginx
  namespace: default
spec:
  containers:
  - image: nginx
    name: mynginx
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: myapp
  name: myapp
spec:
  containers:
  - image: nginx
    name: nginx
  - image: tomcat:8.5.68
    name: tomcat

上面这个运行了两个容器的 Pod,其模型类似于下图

image-20240205165446944

Deployment

Deployment 用来控制 Pod,使 Pod 具有多副本、自愈、扩缩容的能力。

deployment 的 short-name 为 deploy

多副本

kubectl create deployment my-dep --image=nginx --replicas=3
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: my-dep
  name: my-dep
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-dep
  template:
    metadata:
      labels:
        app: my-dep
    spec:
      containers:
      - image: nginx
        name: nginx

扩缩容

kubectl scale --replicas=5 deployment/my-dep
kubectl edit deployment my-dep
​
# 修改 replicas

自愈 & 故障转移

一旦发生某个 Pod 故障的情况,Deployment 就会立刻部署新的 Pod 来实现自愈或故障转移

  • 停机

  • 删除 Pod

  • 容器崩溃

  • ...

滚动更新

# 滚动更新
kubectl set image deployment/my-dep nginx=nginx:1.16.1 --record
​
# 实时查看滚动更新状态
kubectl rollout status deployment/my-dep

版本回退

# 历史记录
kubectl rollout history deployment/my-dep
​
# 查看某个历史详情
kubectl rollout history deployment/my-dep --revision=2
​
# 回滚(回到上次)
kubectl rollout undo deployment/my-dep
​
# 回滚(回到指定版本)
kubectl rollout undo deployment/my-dep --to-revision=2

更多:

除了 Deployment,k8s 还有 StatefulSetDaemonSetJob 等类型资源,称为 工作负载

有状态应用使用 StatefulSet 部署,无状态应用使用 Deployment 部署。

参考:工作负载管理

Service

Service 是将一组 Pods 公开为网络服务的抽象方法。

service 的 short-name 为 svc

ClusterIP

默认的 ServiceType。通过集群的内部 IP 暴露服务,服务只能够在集群内部访问。

# 暴露Deploy
kubectl expose deployment my-dep --port=8000 --target-port=80
​
# 使用标签检索Pod
kubectl get pod -l app=my-dep
apiVersion: v1
kind: Service
metadata:
  labels:
    app: my-dep
  name: my-dep
spec:
  selector:
    app: my-dep
  ports:
  - port: 8000
    protocol: TCP
    targetPort: 80

NodePort

通过每个 Node 节点上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。可以从集群的外部访问每一个 NodePort 服务。

NodePort 需要借助真实存在的 ip,是一个公共的 ip,任何人都可以访问,而 ClusterIP 可以理解成不对外开放,仅限于集群内的节点之间特定的一个范围。

kubectl expose deployment my-dep --port=8000 --target-port=80 --type=NodePort
apiVersion: v1
kind: Service
metadata:
  labels:
    app: my-dep
  name: my-dep
spec:
  ports:
  - port: 8000
    protocol: TCP
    targetPort: 80
  selector:
    app: my-dep
  type: NodePort

如果不手动指定 nodePort,那么将会随机分配 30000-32767 范围内的某个可用端口

负载均衡

Service 天然具备负载均衡的功能,那么具体怎么测试呢?

首先查看 service :

[root@master ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
my-dep       ClusterIP   10.1.7.161     <none>        8000/TCP         60s

可以看到 service 的 ip 为 10.1.7.161,port 为 8000,此时在集群内部可以通过 ip:port 的方式实现负载均衡访问整个服务。

要想看到负载均衡的效果,我们可以将几个 nginx 容器的 index.html 修改一下:

  • 通过 Kubernetes Dashboard 进入容器内部

image-202402051721048

  • 在容器内部执行命令

echo 111 > /usr/share/nginx/html/index.html

image-20240205172112536

  • 同理,在其他几个容器也执行一下

echo 222 > /usr/share/nginx/html/index.html

image-20240205172235799

echo 333 > /usr/share/nginx/html/index.html

image-20240205172301948

接着,通过命令行访问 service 的 ip:port,多执行几次,查看效果:

curl 10.1.7.161:8000

image-20240205172440867

可以看出,service 底层确实帮我们实现了负载均衡!

域名访问

现在可以通过 ip:port 来访问 service,但 ip 是不固定的,如果使用这种方式,那么每次部署的时候 ip 都可能会发生变化,从而依赖它的其他服务需要做相应调整。

那么,k8s 能不能通过域名的方式来访问 serivice 呢?

答案是肯定的,在集群 Pod 内部,相同命名空间下,默认可以通过 service:port 的域名来访问某个 service。

比如,现在我们的 service 名字叫 my-dep,则它的域名就是 my-dep,再加上端口号,就可以直接访问该 service:

my-dep:8000

这种默认的域名方式只能在相同命名空间下的 Pod 容器内部使用,我们现在来创建一个 Pod 测试一下:

kubectl create deployment my-tomcat --image=tomcat:8.5.68

然后进入 tomcat 容器内部执行多次:

curl my-dep:8000

image-20240205185631101

可以看到,通过域名可以直接访问其他 service!

具体原理可以阅读这篇文章:k8s 服务注册与发现(二)Kubernetes内部域名解析原理-腾讯云开发者社区-腾讯云

Ingress

可以看作 Service 的统一网关入口。

Ingress 控制器有不同的实现,典型的有 NginxIstioTraefik 等。

安装

这里以 Nginx 实现为例。

cat > ingress-nginx-controller.yaml << EOF
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
 
---
 
kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
 
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
 
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
 
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
 
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses/status
    verbs:
      - update
 
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get
 
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx
 
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx
---
kind: Service
apiVersion: v1
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  clusterIP: 10.1.211.240
  externalTrafficPolicy: Cluster
  ports:
  - name: http
    nodePort: 31686
    port: 80
    protocol: TCP
    targetPort: http
  - name: https
    nodePort: 30036
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
  sessionAffinity: None
  type: NodePort
---
 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      # wait up to five minutes for the drain of connections
      terminationGracePeriodSeconds: 300
      serviceAccountName: nginx-ingress-serviceaccount
      hostNetwork: true
      containers:
        - name: nginx-ingress-controller
          image: registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 33
            runAsUser: 33
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          lifecycle:
            preStop:
              exec:
                command:
                  - /wait-shutdown
​
---
EOF
​
kubectl apply -f ingress-nginx-controller.yaml
  • 使用

[root@master k8s]# kubectl get all -n ingress-nginx 
NAME                                            READY   STATUS    RESTARTS   AGE
pod/nginx-ingress-controller-84865c44d9-p6ktw   1/1     Running   0          106s

NAME                    TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx   NodePort   10.1.211.240   <none>        80:31686/TCP,443:30036/TCP   106s

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-ingress-controller   1/1     1            1           106s

NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-ingress-controller-84865c44d9   1         1         1       106s

可以看到 Ingress 底层暴露了两个 NodePort,31686 和 30036,分别绑定了内部 nginx 的 80 和 443 端口,那么我们就可以通过集群中任意一个 Node 的这两个端口来访问 Ingress,比如:

使用

  • 先部署 nginx 和 tomcat

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  selector:
    app: my-nginx
  ports:
  - port: 80
    targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-tomcat
spec:
  selector:
    app: my-tomcat
  ports:
  - port: 8080
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      app: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-tomcat
spec:
  selector:
    matchLabels:
      app: my-tomcat
  replicas: 2
  template:
    metadata:
      labels:
        app: my-tomcat
    spec:
      containers:
      - name: my-tomcat
        image: tomcat:8.5.68
        ports:
        - containerPort: 8080
域名访问
  • 编写一个 ingress-example.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: nginx.ingress.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-nginx
                port:
                  number: 80
    - host: tomcat.ingress.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-tomcat
                port:
                  number: 8080
  • 添加 host

192.168.10.101 nginx.ingress.example.com tomcat.ingress.example.com
路径重写
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
    - host: nginx.ingress.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-nginx
                port:
                  number: 80
    - host: tomcat.ingress.example.com
      http:
        paths:
          - path: "/test(/|$)(.*)"
            pathType: Prefix
            backend:
              service:
                name: my-tomcat
                port:
                  number: 8080

尝试访问 tomcat 加上 /test 前缀:http://tomcat.ingress.example.com:31686/test

流量限制
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-limit-rate
  annotations:
    nginx.ingress.kubernetes.io/limit-rps: "1"
spec:
  ingressClassName: my-nginx
  rules:
    - host: nginx.ingress.example.com
      http:
        paths:
          - path: /
            pathType: Exact
            backend:
              service:
                name: my-nginx
                port:
                  number: 80

尝试浏览器一直 F5 刷新访问 nginx:http://nginx.ingress.example.com:31686/

image-20240205232107176

存储抽象

容器中的文件在磁盘上是临时存放的,当容器崩溃时,kubelet 将重启容器,而容器中的文件将会丢失——因为容器会以干净的状态重建。其次,当在一个 Pod 中同时运行多个容器时,常常需要在这些容器之间共享文件。Kubernetes 抽象出 Volume 对象来解决这两个问题。

Docker 中也有 Volume 的概念,在 Docker 中,Volume 是磁盘上或者另一个容器内的一个目录。但 Docker 对它只有少量且松散的管理,并且功能还非常有限(例如,截至 Docker 1.7,每个容器只允许有一个 Volume 驱动程序,并且无法将参数传递给卷)。

Kubernetes 中的卷具有明确的生命周期——与包裹它的 Pod 相同,它比 Pod 中运行的任何容器的存活期都长,在容器重启时数据也会得到保留。当一个 Pod 不存在时,卷也将不再存在。Kubernetes 支持许多类型的卷,Pod 也能同时使用任意数量的卷。

卷的核心是包含一些数据的目录,Pod 中的容器可以访问该目录。特定的卷类型可以决定这个目录如何形成、支持何种介质,以及目录中存放什么内容。

使用卷时,Pod 声明中需要提供卷的类型(.spec.volumes 字段)和卷挂载的位置(.spec.containers.volumeMounts 字段)

Kubernetes 提供了众多的卷类型,包括 emptyDir、hostPath、nfs、glusterfs、cephfs 等。

hostPath

hostPath 卷能将主机节点上的文件或目录挂载到 Pod 中。

apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod
spec:
  containers:
  - name: hostpath-pod
    image: nginx
    volumeMounts:
    - mountPath: /test-nginx
      name: myhostpath
  volumes:
  - name: myhostpath
    hostPath:
      path: /tmp/nginx
      type: DirectoryOrCreate
NFS

很多应用需要在集群内部有一个统一的地方存储文件,比如日志、图片等,而使用 hostPath 方式并不灵活,因为只能存放在与 Pod 相同的节点上,当有多个 Pod 时只能分散存储,无法做到统一存储。而使用 NFS 的方式可以解决这个问题。

1、所有节点

# 所有机器安装
yum install -y nfs-utils rpcbind

2、主节点

# nfs主节点
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports

mkdir -p /nfs/data
systemctl enable rpcbind --now
systemctl enable nfs-server --now
# 配置生效
exportfs -r

3、从节点

# 展示主节点可挂载的目录
showmount -e master

# 挂载 nfs 服务器上的共享目录到本机路径
mkdir -p /nfs/data
mount -t nfs master:/nfs/data /nfs/data

# 写入一个测试文件
echo "hello nfs server" > /nfs/data/test.txt

4、引用 NFS 存储

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-pv-demo
  name: nginx-pv-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-pv-demo
  template:
    metadata:
      labels:
        app: nginx-pv-demo
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
      volumes:
        - name: html
          nfs:
            server: master
            path: /nfs/data/nginx-pv

持久化存储

官方介绍

存储的管理是一个与计算实例的管理完全不同的问题。 PersistentVolume 子系统为用户和管理员提供了一组 API, 将存储如何制备的细节从其如何被使用中抽象出来。 为了实现这点,我们引入了两个新的 API 资源:PersistentVolume 和 PersistentVolumeClaim。

持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class)来动态制备。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。

持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存)。同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以挂载为 ReadWriteOnce、ReadOnlyMany、ReadWriteMany 或 ReadWriteOncePod, 请参阅访问模式)。

尽管 PersistentVolumeClaim 允许用户消耗抽象的存储资源, 常见的情况是针对不同的问题用户需要的是具有不同属性(如,性能)的 PersistentVolume 卷。 集群管理员需要能够提供不同性质的 PersistentVolume, 并且这些 PV 卷之间的差别不仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户。 为了满足这类需求,就有了存储类(StorageClass) 资源。

总结

PV:持久卷(Persistent Volume),将应用需要持久化的数据保存到指定位置 PVC:持久卷申明(Persistent Volume Claim),申明需要使用的持久卷的规格和大小等 StorageClass:存储类,用于自动创建 PV

PV、PVC、StorageClass 的协作过程

image-20240219200808515

PV & PVC

1、创建 PV 池

静态供应

# nfs主节点
mkdir -p /nfs/data/01
mkdir -p /nfs/data/02
mkdir -p /nfs/data/03

创建 PV

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv01-10m
spec:
  capacity:
    storage: 10Mi
  accessModes:
    - ReadWriteMany
  storageClassName: nfs
  nfs:
    path: /nfs/data/01
    server: master
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv02-1g
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  storageClassName: nfs
  nfs:
    path: /nfs/data/02
    server: master
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv03-3g
spec:
  capacity:
    storage: 3Gi
  accessModes:
    - ReadWriteMany
  storageClassName: nfs
  nfs:
    path: /nfs/data/03
    server: master

3、PVC 创建与绑定

创建 PVC

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nginx-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 200Mi
  storageClassName: nfs

创建 Pod 绑定 PVC

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-deploy-pvc
  name: nginx-deploy-pvc
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-deploy-pvc
  template:
    metadata:
      labels:
        app: nginx-deploy-pvc
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
      volumes:
        - name: html
          persistentVolumeClaim:
            claimName: nginx-pvc
StorageClass

什么是 StorageClass

Kubernetes 提供了一套可以自动创建 PV 的机制,即 Dynamic Provisioning,而这个机制的核心在于 StorageClass 这个 API 对象。

StorageClass 对象会定义两部分内容:

  • PV 的属性,比如:存储类型、Volume 的大小等

  • 创建这种 PV 需要用到的存储插件

为什么需要 StorageClass

在一个大规模的 Kubernetes 集群里,可能有成千上万个 PVC,这就意味着运维人员必须创建出这么多个 PV。随着项目的发展,会有新的 PVC 不断地被提交,那么运维人员就需要不断地添加新的满足要求的 PV,否则新的 Pod 就会因为 PVC 绑定不到 PV 而导致创建失败。

此外,不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求。

为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass。通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的特性了,这样就可以根据应用的特性去申请合适的存储资源了。

NFS Provisioner

NFS Provisioner 是一个自动配置卷程序,它使用现有的 NFS 服务器来支持通过持久卷申明来动态配置持久卷。

持久卷会被配置为:{namespace}-{pvcName}-{pvName}。

实战

创建用户和角色 RBAC

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

部署 nfs-client-provisioner

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          #image: registry.cn-shenzhen.aliyuncs.com/hoby/nfs-subdir-external-provisioner:v4.0.0
          image: registry.cn-shenzhen.aliyuncs.com/hoby/nfs-client-provisioner:v3.1.0
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: qgg-nfs-storage  #与storageClass的provisioner配置一致
            - name: NFS_SERVER
              value: master  #NFS服务器的地址
            - name: NFS_PATH
              value: /nfs/data  #NFS服务器的目录
      volumes:
        - name: nfs-client-root
          nfs:
            server: master
            path: /nfs/data

创建 StorageClass

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: qgg-nfs-storage  #与nfs-client-provisioner的环境变量PROVISIONER_NAME一致
allowVolumeExpansion: true
parameters:
  pathPattern: "${.PVC.namespace}-${.PVC.name}"
  onDelete: retain

创建 PVC

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-sc
  annotations:
    volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

创建 Pod

apiVersion: v1
kind: Pod
metadata:
  name: pvc-sc-pod
  labels:
    name: pvc-sc-pod
spec:
  containers:
  - name: pvc-sc-pod
    image: nginx
    command: 
      - "/bin/sh"
    args: 
      - "-c"
      - "touch /mnt/SUCCESS && exit 0 || exit 1"  #创建一个SUCCESS文件后退出
    volumeMounts:
      - name: nfs-pvc
        mountPath: /mnt
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
    ports:
      - containerPort: 80
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: pvc-sc
  restartPolicy: Never

验证结果

[root@master k8s]# kubectl get pod
NAME                                      READY   STATUS      RESTARTS   AGE
nfs-client-provisioner-698559446f-8b5kl   1/1     Running     1          13d
pvc-sc-pod                                0/1     Completed   0          27s

pvc-sc-pod 状态为 Completed,说明任务已经执行完成,退出了容器,此为正常结果。

[root@master k8s]# ls /nfs/data/
default-pvc-sc-pvc-4ebc68ca-b68b-475e-b3e4-ffce442ea512  mysql-pv
[root@master k8s]# ls /nfs/data/default-pvc-sc-pvc-4ebc68ca-b68b-475e-b3e4-ffce442ea512/
SUCCESS

查看 master 的 /nfs/data 文件夹,可以看到该目录下为 pvc-sc-pod 自动创建了一个文件夹,里面创建了一个 SUCCESS 文件。

ConfigMap

ConfigMap 是一种 API 对象,用来将非机密性的数据保存到键值对中。使用时可以用作环境变量、命令行参数或者存储卷中的配置文件。

ConfigMap 将环境配置信息和容器镜像解耦,便于应用配置的修改。

注意:ConfigMap 并不提供保密或加密功能,如果你想存储的数据是机密的,请使用 Secret。

创建一个 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: appvar
data:
  # 类属性键:每一个键都映射到一个简单的值
  player_initial_lives: "3"
  ui_properties_file_name: "user-interface.properties"
  # 类文件键:一个键可以映射多行内容,常用于抽取应用配置
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true
通过环境变量方式使用

Kubernetes 在 1.6 版本引入新字段 envFrom,可以实现在 Pod 环境中将 ConfigMap 中定义的所有 key=value 自动生成为环境变量,代码格式如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: envfrom-cm-test
spec:
  containers:
    - name: envfrom-cm-test
      image: busybox
      command: [ "/bin/sh", "-c", "env" ]
      envFrom:
      - configMapRef:
          name: appvar
  restartPolicy: Never
Redis 示例

创建 ConfigMap

apiVersion: v1
data:  #data是所有真正的数据,key:默认是文件名,value:配置文件的内容
  redis.conf: |
    appendonly yes
kind: ConfigMap
metadata:
  name: redis-conf
  namespace: default

创建 Pod

apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: redis
    command:
      - redis-server
      - "/redis-master/redis.conf"  #指的是redis容器内部的位置
    ports:
    - containerPort: 6379
    volumeMounts:
    - mountPath: /data
      name: data
    - mountPath: /redis-master
      name: config
  volumes:
    - name: data
      emptyDir: {}
    - name: config
      configMap:
        name: redis-conf
        items:
        - key: redis.conf
          path: redis.conf

检查默认配置

kubectl exec -it redis -- redis-cli

127.0.0.1:6379> CONFIG GET appendonly
1) "appendonly"
2) "yes"

修改 ConfigMap

apiVersion: v1
data:  #data是所有真正的数据,key:默认是文件名,value:配置文件的内容
  redis.conf: |
    appendonly yes
    maxmemory 2mb
    maxmemory-policy allkeys-lru
kind: ConfigMap
metadata:
  name: redis-conf
  namespace: default

检查配置是否更新

kubectl exec -it redis -- redis-cli

127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"
2) "0"
127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"

配置值未更改,因为需要重新启动 Pod 才能从关联的 ConfigMap 中获取更新的值。 原因:我们的Pod部署的中间件自己本身没有热更新能力。

如何重启 Pod

kubectl get pod {podname} -n {namespace} -o yaml | kubectl replace --force -f -
kubectl get pod redis -n default -o yaml | kubectl replace --force -f -

重启 Pod 之后配置生效:

image-20240220125511658

Secret

Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 SSH 密钥。 将这些信息放在 Secret 中比放在 Pod 的定义或者容器镜像中来说更加安全和灵活。

镜像仓库 Secret

创建

kubectl create secret docker-registry regcred \
  --docker-server=<你的镜像仓库服务器> \
  --docker-username=<你的用户名> \
  --docker-password=<你的密码> \
  --docker-email=<你的邮箱地址>

引用

apiVersion: v1
kind: Pod
metadata:
  name: private-nginx
spec:
  containers:
  - name: private-nginx
    image: hobyy/kubeblog:1.0
  imagePullSecrets:
  - name: my-docker
通用 Secret

从文本文件创建

echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txt
​
kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt

默认的键名是文件名,你也可以使用 [–from-file=[key=]source] 参数来设置键名。

kubectl create secret generic db-user-pass \
  --from-file=username=./username.txt \
  --from-file=password=./password.txt

从命令行创建

kubectl create secret generic dev-db-secret \
  --from-literal=username='devuser' \
  --from-literal=password='S!B\*d$zDsb='

从 yaml 创建

apiVersion: v1
kind: Secret
metadata:
  name: user-password-secret
type: Opaque
data:
  username: cm9vdA==
  password: bGl1bWlhb2Nu
环境变量方式引用
apiVersion: v1
kind: Pod
metadata:
  name: secrets-test-pod
spec:
  containers:
    - name: busybox-container
      image: busybox
      command: ["sleep", "1000"]
      env:
        - name: ENV_VAR_USERNAME
          valueFrom:
            secretKeyRef:
              name: user-password-secret
              key: username
        - name: ENV_VAR__PASSWORD
          valueFrom:
            secretKeyRef:
              name: user-password-secret
              key: password
  restartPolicy: Never

常见问题

coredns 内网转发出现5s卡顿

修改 coredns 配置,添加一行配置来禁用 ipv6 解析:rewrite stop type AAAA A

kubectl edit cm -n kube-system coredns

image-20240205184325601

接着重启来使配置生效:

kubectl rollout restart deployment coredns -n kube-system
  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值