MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

基于 Kubernetes 的分布式容器管理

2023-06-036.8k 阅读

1. 分布式系统与容器化概述

在当今数字化时代,随着业务规模的不断扩大和用户需求的日益增长,分布式系统已成为构建大规模、高性能应用的关键技术。分布式系统通过将任务分散到多个节点上执行,以提高系统的可用性、可扩展性和性能。然而,管理分布式系统中的众多节点和应用程序变得极具挑战性。

容器化技术应运而生,它为解决这些挑战提供了有效途径。容器是一种轻量级的虚拟化技术,它将应用程序及其依赖项打包在一起,形成一个独立的运行单元。这种隔离性使得应用程序可以在不同的环境中无缝运行,而不会受到环境差异的影响。例如,一个用 Python 编写的 Web 应用,连同其所需的 Python 运行时、各种库以及配置文件,都可以打包到一个容器中。无论在开发环境、测试环境还是生产环境,只要有支持容器运行的平台,这个容器就能以相同的方式运行。

2. Kubernetes 基础

Kubernetes(简称 K8s)是一个开源的容器编排引擎,用于自动化部署、扩展和管理容器化应用程序。它在分布式系统的容器管理中扮演着核心角色。

2.1 Kubernetes 架构

Kubernetes 采用主从架构,主要由控制平面(Master)和工作节点(Node)组成。

  • 控制平面:负责整个集群的管理和决策。它包含多个关键组件,如 API Server,它是 Kubernetes 系统的入口,所有对集群的操作都通过 API Server 进行;Scheduler 负责将 Pod 调度到合适的工作节点上;Controller Manager 则负责监控集群状态,并对各种资源进行管理和维护,确保实际状态与期望状态一致。
  • 工作节点:负责运行容器化应用程序。每个工作节点上都运行着 kubelet 组件,它负责与控制平面通信,接收并执行控制平面下发的任务,同时管理节点上的容器。此外,工作节点还运行着容器运行时,如 Docker,负责实际的容器创建、启动和停止等操作。

2.2 Kubernetes 核心资源对象

  • Pod:是 Kubernetes 中最小的可部署和可管理的计算单元。一个 Pod 可以包含一个或多个紧密相关的容器,这些容器共享网络命名空间和存储卷。例如,一个 Web 应用可能由一个前端容器和一个后端 API 容器组成,它们可以被封装在同一个 Pod 中,共享网络,方便彼此通信。下面是一个简单的 Pod 定义示例(以 YAML 格式为例):
apiVersion: v1
kind: Pod
metadata:
  name: my - pod
spec:
  containers:
  - name: my - container
    image: nginx:1.14.2
    ports:
    - containerPort: 80

在这个示例中,定义了一个名为 my - pod 的 Pod,其中包含一个名为 my - container 的容器,使用的镜像为 nginx:1.14.2,并将容器的 80 端口暴露出来。

  • Service:为一组 Pod 提供一个稳定的网络端点。它可以将外部流量路由到对应的 Pod 上,实现服务发现和负载均衡。例如,当有多个运行相同 Web 应用的 Pod 时,通过 Service 可以将外部请求均匀地分发到这些 Pod 上。Service 有多种类型,如 ClusterIP 类型的 Service 用于在集群内部提供服务,NodePort 类型的 Service 则可以将服务暴露到集群外部节点的指定端口上。以下是一个 ClusterIP 类型 Service 的定义示例:
apiVersion: v1
kind: Service
metadata:
  name: my - service
spec:
  selector:
    app: my - app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP

这个 Service 定义通过 selector 选择了标签为 app: my - app 的 Pod,将集群内部发往 80 端口的 TCP 流量转发到这些 Pod 的 80 端口上。

  • Deployment:用于管理 Pod 的生命周期,包括创建、更新和回滚等操作。它通过定义一个 Pod 模板,来创建和管理一组具有相同特征的 Pod。例如,当需要更新应用程序版本时,可以通过修改 Deployment 的 Pod 模板中的镜像版本,Kubernetes 会自动将旧版本的 Pod 替换为新版本的 Pod。以下是一个 Deployment 的定义示例:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my - deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my - app
  template:
    metadata:
      labels:
        app: my - app
    spec:
      containers:
      - name: my - container
        image: nginx:1.14.2
        ports:
        - containerPort: 80

在这个 Deployment 定义中,指定了要创建 3 个副本的 Pod,这些 Pod 的标签为 app: my - app,并且 Pod 模板中定义了容器使用 nginx:1.14.2 镜像,暴露 80 端口。

3. 基于 Kubernetes 的分布式容器管理实践

3.1 应用部署

在 Kubernetes 中部署应用程序通常从创建 Deployment 开始。假设我们有一个简单的 Python Flask 应用,代码如下(app.py):

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello, World!'


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

首先,我们需要为这个应用创建一个 Docker 镜像。在应用根目录下创建一个 Dockerfile

FROM python:3.8 - slim

WORKDIR /app

COPY requirements.txt.
RUN pip install -r requirements.txt

COPY.

EXPOSE 5000

CMD ["python", "app.py"]

假设 requirements.txt 中只包含 flask 依赖。然后,使用以下命令构建镜像:

docker build -t my - flask - app:v1.0.0.

接下来,创建一个 Kubernetes Deployment 来部署这个应用。创建一个 deployment.yaml 文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my - flask - deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my - flask - app
  template:
    metadata:
      labels:
        app: my - flask - app
    spec:
      containers:
      - name: my - flask - container
        image: my - flask - app:v1.0.0
        ports:
        - containerPort: 5000

使用 kubectl apply -f deployment.yaml 命令来创建 Deployment,Kubernetes 会根据定义创建两个运行我们 Flask 应用的 Pod。

为了让外部能够访问这个应用,我们需要创建一个 Service。创建 service.yaml 文件:

apiVersion: v1
kind: Service
metadata:
  name: my - flask - service
spec:
  selector:
    app: my - flask - app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5000
  type: NodePort

使用 kubectl apply -f service.yaml 创建 Service 后,通过任意节点的 NodePort(在 Service 定义中未指定时,Kubernetes 会自动分配一个 30000 - 32767 之间的端口)就可以访问到我们的 Flask 应用。

3.2 应用扩展

随着应用的流量增加,我们可能需要增加 Pod 的副本数量以提高应用的处理能力。在 Kubernetes 中,扩展应用非常简单。可以通过修改 Deployment 的 replicas 字段来实现,例如将 deployment.yaml 中的 replicas 从 2 改为 5:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my - flask - deployment
spec:
  replicas: 5
  selector:
    matchLabels:
      app: my - flask - app
  template:
    metadata:
      labels:
        app: my - flask - app
    spec:
      containers:
      - name: my - flask - container
        image: my - flask - app:v1.0.0
        ports:
        - containerPort: 5000

然后再次执行 kubectl apply -f deployment.yaml,Kubernetes 会自动创建额外的 3 个 Pod,使应用的副本数量达到 5 个,从而提高了应用的处理能力。

另外,Kubernetes 还支持基于指标的自动扩展,例如根据 CPU 使用率或内存使用率自动调整 Pod 的副本数量。可以通过创建 Horizontal Pod Autoscaler(HPA)来实现。首先,确保集群安装了 metrics - server,它用于收集 Pod 和 Node 的资源使用指标。然后创建一个 hpa.yaml 文件:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: my - flask - hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my - flask - deployment
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

使用 kubectl apply -f hpa.yaml 创建 HPA 后,Kubernetes 会根据 my - flask - deployment 中 Pod 的 CPU 使用率自动调整 Pod 的副本数量,使其保持在 2 到 10 个之间,以满足应用的负载需求。

3.3 应用更新与回滚

当我们有新的应用版本时,需要对应用进行更新。假设我们的 Flask 应用有了新的版本,更新了 app.py 中的返回内容:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello, New World!'


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

重新构建 Docker 镜像:

docker build -t my - flask - app:v1.1.0.

然后修改 deployment.yaml 中的镜像版本:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my - flask - deployment
spec:
  replicas: 5
  selector:
    matchLabels:
      app: my - flask - app
  template:
    metadata:
      labels:
        app: my - flask - app
    spec:
      containers:
      - name: my - flask - container
        image: my - flask - app:v1.1.0
        ports:
        - containerPort: 5000

执行 kubectl apply -f deployment.yaml,Kubernetes 会逐步将旧版本的 Pod 替换为新版本的 Pod,实现滚动更新。在更新过程中,如果发现新版本出现问题,可以随时回滚到上一个版本。例如,执行 kubectl rollout undo deployment my - flask - deployment,Kubernetes 会将 Deployment 回滚到上一个版本,将新版本的 Pod 替换为旧版本的 Pod,确保应用的正常运行。

4. Kubernetes 网络模型

在分布式容器管理中,网络是至关重要的一环。Kubernetes 采用了一种扁平的网络模型,旨在确保每个 Pod 都有一个独立的 IP 地址,并且 Pod 之间可以直接进行通信,无需通过 NAT 等复杂的网络转换。

4.1 Pod 网络

每个 Pod 都有自己的 IP 地址,同一 Node 上的 Pod 可以通过本地网络直接通信。不同 Node 上的 Pod 之间通信则依赖于集群网络插件,如 Flannel、Calico 等。这些网络插件负责在不同 Node 之间建立网络隧道,实现 Pod 之间的跨节点通信。

以 Flannel 为例,它在每个 Node 上运行一个 flanneld 进程,通过 Etcd 存储集群的网络配置信息。Flannel 为每个 Node 分配一个子网,Node 上的 Pod 从该子网中获取 IP 地址。当不同 Node 上的 Pod 通信时,Flannel 通过 VXLAN 等隧道技术将数据包封装后发送到目标 Node,目标 Node 再解封装并将数据包转发到目标 Pod。

4.2 Service 网络

Service 为一组 Pod 提供了一个稳定的网络端点。当客户端访问 Service 时,Kubernetes 通过 iptables 等机制将请求转发到后端的 Pod 上。对于 ClusterIP 类型的 Service,它在集群内部有一个虚拟的 IP 地址,只有集群内部的 Pod 和 Service 可以访问。NodePort 类型的 Service 则将服务暴露到集群外部节点的指定端口上,外部客户端可以通过 <NodeIP>:<NodePort> 来访问服务。

例如,在前面的 Flask 应用示例中,my - flask - service 是一个 NodePort 类型的 Service,外部客户端可以通过任意节点的 NodePort 访问到运行 Flask 应用的 Pod。如果是 ClusterIP 类型的 Service,假设集群内部有另一个 Pod 需要调用 my - flask - service,它只需要通过 Service 的 ClusterIP 和端口就可以进行访问,而无需关心具体是哪些 Pod 在提供服务。

5. Kubernetes 存储管理

在分布式容器管理中,存储管理同样重要。应用程序可能需要持久化数据,如数据库的存储、文件上传等。Kubernetes 提供了多种存储解决方案。

5.1 卷(Volume)

卷是 Kubernetes 中用于在 Pod 内的容器之间共享数据的存储机制。它可以是多种类型,如 EmptyDir 卷、HostPath 卷、PersistentVolume 等。

  • EmptyDir 卷:在 Pod 创建时被创建,当 Pod 被删除时,该卷也会被删除。它主要用于在 Pod 内的多个容器之间共享临时数据。例如,一个容器生成的数据需要被另一个容器处理,就可以使用 EmptyDir 卷来共享数据。以下是一个包含 EmptyDir 卷的 Pod 定义示例:
apiVersion: v1
kind: Pod
metadata:
  name: volume - pod
spec:
  containers:
  - name: producer
    image: busybox
    command: ["sh", "-c", "echo 'Hello from producer' > /shared/data.txt"]
    volumeMounts:
    - name: shared - volume
      mountPath: /shared
  - name: consumer
    image: busybox
    command: ["sh", "-c", "cat /shared/data.txt"]
    volumeMounts:
    - name: shared - volume
      mountPath: /shared
  volumes:
  - name: shared - volume
    emptyDir: {}

在这个示例中,producer 容器将数据写入 EmptyDir 卷挂载的 /shared 目录,consumer 容器从该目录读取数据。

  • HostPath 卷:将 Node 上的文件系统路径挂载到 Pod 中。它适用于需要访问 Node 本地文件的场景,如日志收集等。但是,由于不同 Node 上的文件系统可能存在差异,使用 HostPath 卷可能会导致应用在不同 Node 上的行为不一致。以下是一个使用 HostPath 卷的 Pod 定义示例:
apiVersion: v1
kind: Pod
metadata:
  name: hostpath - pod
spec:
  containers:
  - name: my - container
    image: nginx:1.14.2
    volumeMounts:
    - name: my - volume
      mountPath: /var/log/nginx
  volumes:
  - name: my - volume
    hostPath:
      path: /var/log/nginx

在这个示例中,将 Node 上的 /var/log/nginx 目录挂载到 Pod 中 nginx 容器的 /var/log/nginx 目录,使得容器内的日志可以直接存储在 Node 上。

5.2 持久化卷(PersistentVolume)与持久化卷声明(PersistentVolumeClaim)

PersistentVolume(PV)是集群中由管理员预先配置的一段存储,它是集群资源。PersistentVolumeClaim(PVC)是用户对存储的请求,PVC 会绑定到合适的 PV 上。这种机制使得应用程序的存储请求与实际的存储实现解耦,用户只需要关心 PVC,而无需关心具体的 PV 配置。

例如,假设我们有一个需要持久化存储的 MySQL 应用。首先,管理员创建一个 PV:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my - pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  hostPath:
    path: /data/mysql

这个 PV 定义了 10GB 的存储空间,访问模式为 ReadWriteOnce(表示只能被一个 Node 以读写方式挂载),回收策略为 Recycle(表示当 PVC 被删除时,PV 会被回收并清理数据),存储路径为 Node 上的 /data/mysql

然后,用户创建一个 PVC 来请求存储:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my - pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

这个 PVC 请求了 5GB 的存储空间,访问模式与 PV 匹配。Kubernetes 会自动将这个 PVC 绑定到合适的 PV 上(在这个例子中就是 my - pv)。

最后,在 MySQL 的 Deployment 中使用这个 PVC:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql - deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        - name: MYSQL_DATABASE
          value: mydb
        - name: MYSQL_USER
          value: myuser
        - name: MYSQL_PASSWORD
          value: mypassword
        volumeMounts:
        - name: mysql - data
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql - data
        persistentVolumeClaim:
          claimName: my - pvc

在这个 Deployment 中,将 my - pvc 挂载到 MySQL 容器的 /var/lib/mysql 目录,实现了 MySQL 数据的持久化存储。

6. Kubernetes 安全管理

在分布式容器环境中,安全至关重要。Kubernetes 提供了一系列安全机制来保障集群和应用的安全。

6.1 身份认证

Kubernetes 支持多种身份认证方式,如客户端证书认证、Token 认证、OpenID Connect 等。

  • 客户端证书认证:通过客户端证书来验证用户身份。客户端在请求 API Server 时,需要提供证书进行身份验证。管理员可以通过 openssl 等工具生成客户端证书和私钥。然后在配置 kubectl 时,指定证书和私钥路径,例如:
kubectl config set - credentials my - user --client - cert - file=/path/to/cert.pem --client - key - file=/path/to/key.pem
  • Token 认证:通过 Token 来验证用户身份。Token 可以是静态的,也可以是动态生成的。例如,ServiceAccount 会自动生成 Token,Pod 内的容器可以通过挂载 ServiceAccount 的 Token 文件来获取 Token,并使用该 Token 访问 API Server。在 Pod 定义中,可以通过以下方式挂载 ServiceAccount 的 Token:
apiVersion: v1
kind: Pod
metadata:
  name: my - pod
spec:
  serviceAccountName: my - sa
  containers:
  - name: my - container
    image: nginx:1.14.2

在这个示例中,my - pod 使用了 my - sa ServiceAccount,Pod 内的容器可以在 /var/run/secrets/kubernetes.io/serviceaccount/token 路径下获取 Token。

6.2 授权

Kubernetes 支持多种授权模式,如 Node 授权、ABAC(Attribute - Based Access Control)、RBAC(Role - Based Access Control)等。其中,RBAC 是最常用的授权模式。

RBAC 通过定义角色(Role)和角色绑定(RoleBinding)来控制用户对资源的访问权限。例如,创建一个只读角色:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: my - namespace
  name: read - only - role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]

这个角色定义在 my - namespace 命名空间内,允许对 pods 资源执行 getlistwatch 操作。

然后创建一个角色绑定,将这个角色绑定给某个用户或 ServiceAccount:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: my - namespace
  name: read - only - binding
subjects:
- kind: User
  name: my - user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: read - only - role
  apiGroup: rbac.authorization.k8s.io

在这个角色绑定中,将 read - only - role 角色绑定给了 my - user 用户,使得 my - user 用户在 my - namespace 命名空间内对 pods 资源具有只读权限。

6.3 网络安全策略

Kubernetes 支持网络安全策略(NetworkPolicy)来控制 Pod 之间的网络流量。例如,创建一个网络安全策略,只允许特定命名空间内的 Pod 访问某个服务:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my - network - policy
  namespace: my - namespace
spec:
  podSelector:
    matchLabels:
      app: my - app
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          project: my - project
      podSelector:
        matchLabels:
          role: backend

在这个网络安全策略中,选择了标签为 app: my - app 的 Pod,只允许来自标签为 project: my - project 的命名空间且标签为 role: backend 的 Pod 访问这些 Pod,从而增强了网络安全性。

通过上述从基础概念到实践应用,再到网络、存储和安全管理等多方面的介绍,我们对基于 Kubernetes 的分布式容器管理有了较为全面和深入的了解。在实际应用中,需要根据具体的业务需求和场景,灵活运用这些知识,构建高效、稳定、安全的分布式容器化应用。