peng-yin / note

:shipit: This is My GitHub Pages
https://note-seven-rho.vercel.app/
8 stars 0 forks source link

Kubernetes 使用总结 #87

Open peng-yin opened 2 years ago

peng-yin commented 2 years ago

Kubernetes 使用总结

Kubernetes 简称 k8s,是 google 在 2014 年发布的一个开源项目。

Kubernetes 解决了哪些问题?

真实的生产环境应用会包含多个容器,而这些容器还很可能会跨越多个服务器主机部署。Kubernetes 提供了为那些工作负载大规模部署容器的编排与管理能力。Kubernetes 编排让你能够构建多容器的应用服务,在集群上调度或伸缩这些容器,以及管理它们随时间变化的健康状态。

Kubernetes 基础概念很多,第一次接触容易看晕,如果是新手,建议直接看实战部分,先跑起来再说。

kubernetes 基础

k8s 中有几个重要概念。

概念 介绍
cluster 一个 k8s 集群
master 集群中的一台机器,是集群的核心,负责整个集群的管理和控制
node 集群中的一台机器,是集群中的工作负载节点
pod k8s 最小调度单位,每个 pod 包含一个或多个容器
label 一个 key = value 键值对,可以附加到各种资源对象上,方便其他资源进行匹配
controller k8s 通过 controller 来管理 pod
service 对外提供统一的服务,自动将请求分发到正确的 pod 处
namespace 将 cluster 逻辑上划分成多个虚拟 cluster,每个 cluster 就是一个 namespace

Cluster

Cluster 代表一个 k8s 集群,是计算、存储和网络资源的集合,k8s 利用这些资源运行各种基于容器的应用。

Master 节点

Master 是 cluster 的大脑,运行着的服务包括 kube-apiserver、kube-scheduler、kube-controller-manager、etcd 和 pod 网络。

Node 节点

Node 节点 是 pod 运行的地方。node 节点上运行的 k8s 组件有 kubelet、kube-proxy 和 pod 网络。

Pod

每一个 pod 包含一个或多个 container,pod 中的容器作为一个整体被 master 调度到 node 节点上运行。

为什么 k8s 使用 pod 管理容器,而不是直接操作容器?

1、因为有些容器天生就是需要紧密的联系,放在一个 pod 中表示一个完整的服务,k8s 同时会在每个 pod 中加入 pause 容器,来管理内部容器组的状态。

2、Pod 中的所有容器共享 ip,共享 volume,方便进行容器之间通信和数据共享。

:::tip 什么时候需要在 pod 中定义多个容器?

答:这些容器联系非常紧密,而且需要直接共享资源,例如一个爬虫程序,和一个 web server 程序。web server 强烈依赖爬虫程序提供数据支持。 :::

Label

Label 是一个 key = value 的键值对,其中 key 和 value 都由用户自己指定。

Label 的使用场景:

总之,label 可以给对象创建多组标签,label 和 label selecter 共同构建了 k8s 系统中核心的应用模型,使得被管理对象能够被精细地分组管理,同时实现了整个集群的高可用性。

Controller

k8s 通常不会直接创建 pod,而是通过 controller 来管理 pod。controller 中定义了 pod 的部署特性,比如有几个副本,在什么样的 node 上运行等。为了满足不同的业务场景,k8s 提供了多种类型的 controller。

:::tip 提示 使用 deployment controller 创建的用例,如果出现有 pod 挂掉的情况,会自动新建一个 pod,来维持配置中的 pod 数量。 :::

Job

容器按照持续运行时间可分为两类:服务类容器和工作类容器。

服务类容器通常持续提供服务,需要一直运行,比如 http server。工作类容器则是一次性任务,比如批处理程序,完成后容器就退出。

Controller 中 deployment、replicaSet 和 daemonSet 类型都用于管理服务类容器,对于工作类容器,我们使用 job。

Service

Service 是可以访问一组 pod 的策略,通常称为微服务。具体访问哪一组 pod 是通过 label 匹配出来的。service 为 pod 提供了负载均衡,原理是使用 iptables。

为什么要用 service ?

外网如何访问 service?

Namespace

如果有多个用户使用同一个 k8s cluster,如何将他们创建的 controller,pod 等资源分开呢?

答:使用 namespace。

如果将物理的 cluster 逻辑上划分成多个虚拟 cluster,每个 cluster 就是一个 namespace,不同 namespace 里的资源是完全隔离的。

k8s 默认创建了两个 namespace。

Kubernetes 优化

健康检查

强大的自愈能力是 k8s 这类容器编排引擎的一个重要特性。自愈的默认实现方式是自动重启发生故障的容器。除此之外,用户还可以利用 liveness 和 readiness 探测机制设置更精细的健康检查,进而实现如下需求:

默认情况下,只有容器进程返回值非零,k8s 才会认为容器发生了故障,需要重启。如果我们想更加细粒度的控制容器重启,可以使用 liveness 和 readiness。

liveness 和 readiness 的原理是定期检查 /tmp/healthy 文件是否存在,如果存在即认为程序没有出故障,如果文件不存在,则会采取相应的措施来进行反馈。

liveness 采取的策略是重启容器,而 readiness 采取的策略是将容器设置为不可用。

:::tip 如果需要在特定情况下重启容器,可以使用 liveness。

如果需要保证容器一直可以对外提供服务,可以使用 readiness。 :::

我们可以将 liveness 和 readiness 配合使用,使用 liveness 判断容器是否需要重启,用 readiness 判断容器是否已经准备好对外提供服务。

数据管理

上文说道,pod 可能会被频繁地销毁和创建,当容器销毁时,保存在容器内部文件系统中的数据都会被清除。为了持久化保存容器的数据,可以使用 k8s volume。

Volume 的生命周期独立于容器,pod 中的容器可能被销毁和重建,但 volume 会被保留。实质上 vloume 是一个目录,当 volume 被 mount 到 pod,pod 中的所有容器都可以访问到这个 volume。

Volume 支持多种类型。

Volume 提供了对各种类型的存放方式,但容器在使用 volume 读写数据时,不需要关心数据到底是存放在本地节点的系统中还是云硬盘上。对容器来说,所有类型的 volume 都只是一个目录。

密码管理

应用程序在启动过程中可能需要一些敏感信息,比如访问数据库的用户名和密码。将这些信息直接保存在容器镜像中显然不妥,k8s 提供的解决方案是 secret。

secret 会以密文的方式存储数据,避免直接在配置文件中保存敏感信息。secret 会以 volume 的形式被 mount 到 pod,容器可通过文件的方式使用 secret 中的敏感数据,此外容器也可以按环境变量的形式使用这些数据。

使用配置文件创建 mysecret.yaml:

apiVersion: v1
kind Secret
metadata:
  name:mysecret
data:
  username:admin
  password:123

保存配置文件后,然后执行kubectl apply -f mysecret.yaml进行创建。

在 pod 中使用创建好的 secret:

# mypod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: mypod
      image: mengsixing/notepad
  volumeMounts:
    - name: foo
      mountPath: "etc/foo"
      readOnly: true
  volumes:
    - name: foo
      secret:
        secretName: mysecret

执行kubectl apply -f mypod.yaml 创建 pod,并使用 secret。创建完成后,secret 保存在容器内 /etc/foo/username ,/etc/foo/password 目录下。

集群监控

创建 k8s 集群并部署容器化应用只是第一步。一旦集群运行起来,我们需要确保集群一切都是正常的,这就需要对集群进行监控。

常用的可视化监控工具如下。

具体的使用步骤就直接看文档了,这里不详细说明。

通过集群监控我们能够及时发现集群出现的问题,但为了方便进一步排查问题,我们还需要进行进行日志记录。

常用的日志管理工具如下。

Kubernetes 实战

我们来实战部署一个 k8s 记事本项目,项目使用 mengsixing/notepad 镜像进行构建,该镜像在部署后会在 8083 端口上提供一个 web 版记事本服务,查看演示

为了避免安装 k8s 出现的各种坑,这里使用 Play with Kubernetes进行演示。

首先在 Play with Kubernetes 上创建 3 台服务器,node1 作为 master 节点,node2 和 node3 作为工作节点。接下来进行以下操作;

创建一个集群 cluster

在 node1 上运行 kubeadm init 即可创建一个集群。

kubeadm init --apiserver-advertise-address $(hostname -i)

执行完成后会生成 token,这样其他节点就可以凭借这个 token 加入该集群。

Kubernetes 实战步骤1

加入 node 节点

在 node2 和 node3 机器上,分别执行以下命令,加入到 node1 的集群。

kubeadm join 192.168.0.8:6443 --token nfs9d0.z7ibv3xokif1mnmv \
    --discovery-token-ca-cert-hash sha256:6587f474ae1543b38954b0e560832ff5b7c67f79e1d464e7f59e33b0fefd6548

命令执行完毕后,即可看到 node2,node3 已经加入成功。

Kubernetes 实战步骤2

查看集群状态

在 node1 节点上,执行以下命令。

kubectl get node

Kubernetes 实战步骤3

可以看到,集群中存在 node1 ,node2,node3 这 3 个节点,但这 3 个节点的都是 NotReady 状态,为什么?

答:因为没有创建集群网络。

创建集群网络

执行以下代码创建集群网络。

kubectl apply -n kube-system -f \
    "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 |tr -d '\n')"

执行命令后,稍等一下,然后查看 node 状态,可以看到,集群中的 3 个节点都是 Ready 状态了。

Kubernetes 实战步骤4

创建 Deployment

我们通过配置文件来创建 deployment,新建 deployment.yaml 文件,内容如下:

# 配置文件格式的版本
apiVersion: apps/v1
# 创建的资源类型
kind: Deployment
# 资源的元数据
metadata:
  name: notepad
# 规格说明
spec:
  # 定义 pod 数量
  replicas: 3
  # 通过 label 找到对应的 pod
  selector:
    matchLabels:
      app: mytest
  # 定义 pod 的模板
  template:
    # pod 的元数据
    metadata:
      # 定义 pod 的 label
      labels:
        app: mytest
    # 描述 pod 的规格
    spec:
      containers:
        - name: notepad
          image: mengsixing/notepad
          ports:
            - containerPort: 8083

文件创建之后,执行命令:

kubectl apply -f ./deployment.yaml
# deployment.apps/notepad created

查看部署的 pod。

Kubernetes 实战步骤4.1

可以看到,我们使用 deployment 类型的 controller 创建了 3 个 pod,分别运行在 node2 和 node3 机器上,如果有更多的机器,也会自动负载均衡,合理地分配到每个机器上。

创建 Service

创建 service 和 deployment 类似,新建 service.yaml 文件,内容如下:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  # 在节点上部署访问 pod 的端口
  type: NodePort
  ports:
    - port: 80
      # service 代理的端口
      targetPort: 8083
      # node 节点上提供给集群外部的访问端口
      nodePort: 30036
  # 匹配 pod
  selector:
    app: mytest

文件创建之后,执行命令:

kubectl apply -f ./service.yaml
# service/my-service created

查看创建结果

使用 kubectl get deployment 和 kubectl get service 查看创建结果。

Kubernetes 实战步骤5

可以看到,deployment 和 service 均创建成功,并且已知 service 暴露的 ip 地址为:10.106.74.65,端口号为 80,由于设置了 targetPort,service 在接收到请求时,会自动转发到 pod 对应的 8083 端口上。

访问部署结果

部署成功后,我们可以通过两种方式进行访问:

# 集群内访问
curl 10.106.74.65
# 集群外访问
# 192.168.0.12 是 node2 节点的ip
# 192.168.0.11 是 node3 节点的ip
curl 192.168.0.12:30036
curl 192.168.0.11:30036

集群内访问。

Kubernetes 实战步骤6

集群外访问。

Kubernetes 实战步骤6

到这里,已经算部署成功了,大家肯定有疑问,部署一个如此简单的 web 应用就这么麻烦,到底 k8s 好在哪里?我们接着往下看。

K8s 运维

项目已经部署,接下来来实战一个运维,感受一下 k8s 带给我们的便利。

案例 1

公司要做双 11 活动,需要至少 100 个容器才能满足用户要求,应该怎么做?

首先,应该尽可能利用当前拥有的服务器资源,创建更多的容器来参与负载均衡,通过 docker stats 可以查看容器占用的系统资源情况。如果充分利用后仍然不能满足需求,就根据剩余需要的容器,计算出需要购买多少机器,实现资源的合理利用。

执行以下命令就能将容器扩展到 100 个。

kubectl scale deployments/notepad --replicas=100

如图,扩展 10 个 pod 的情况,node2 和 node3 节点共同分担负载。

Kubernetes 实战步骤8

也可以通过修改 deployment.yaml 中的 replicas 字段,执行 kubectl apply -f deployment.yaml去执行扩展。如果活动结束了,只需要将多余的服务器删除,缩减容器数量即可还原到之前的效果。

案例 2

双 11 活动很火爆,但突然加了需求,需要紧急上线,如果实现滚动更新?

滚动更新就是在不宕机的情况下,实现代码更新。执行以下命令,修改 image 即可。

kubectl set image deployments/notepad notepad=mengsixing/notepad:new

也可以通过修改 deployment.yaml 中的 image 字段,执行 kubectl apply -f deployment.yaml去执行升级。

如果更新出了问题,k8s 内置了一键还原上个版本的命令:

kubectl rollout undo deployments/notepad

通过这 2 个案例,感觉到了 k8s 给运维带来了很大的便利:快速高效地部署项目支持动态扩展滚动升级同时还能按需优化使用的硬件资源

总结

本文的目的就是入门 k8s,通过一个简单的集群来实现这一点,但其中也踩了好多坑,具体如下:

最后,推荐一下《每天 5 分钟玩转 Kubernetes》这本书,一本非常适合入门 k8s 的实战书。书中通过大量的简单实战,从易到难,让我真正理解了 k8s,本文中的大量理论知识也都来自这本书。