工作需要学习 Kubernetes。
1. K8s 用来干什么的
由于容器技术,一个服务器上可以运行多个容器。如何管理这些容器就成了一个问题。而 k8s 就是为了解决这个问题诞生的。它可以弹性,高效地管理容器集群。
1.1 K8s 的优点
1. 轻量级: 采用 go 语言编写,效率高
2. 开源
3. 弹性管理:当容器增多和减少时,可以很简单的改变资源的利用。
4. 负载均衡
复制代码
2. K8s 做了什么
首先我们有容器化的应用,然后我们有了 K8s 集群。然后我们将容器化应用部署到集群上。由于容器化应用自带环境,与计算机环境是分离的。因此可以在集群中自由调动。而 K8s 让这种调用更加高效和自动化。
3. k8s 的结构
一个 k8s 集群由 master 和 nodes 构成。简单来说 master 用于管理集群,node 用于托管应用。
- master 协调集群中的所有活动。
- node 则是一个虚拟机或者物理计算机,充当的是工作机器的作用。每一个 node 中又含有 kubelet,这个 kubelet 一边管理这个 node,一边与 master 通信。当然还有处理容器操作的工具,如 Docker。一般来说,生产环境中的 k8s 至少得有 3 个 node。
在 k8s 中部署应用的时候,首先告诉 master 要部署了,于是 master 就编排容器在 node 上运行。node 也可以通过 master 暴露出来的 Kubernetes API 与 master 进行通信。当然,终端用户也可以使用这个 API 来调控整个集群。
3.1 kubectl 的初步使用
- 查看版本信息:
kubectl version
,这没什么好说的 - 查看集群信息:
kubectl cluster-info
- 查看 node 信息:
kubectl get nodes
,可以看到各个 node 的信息。
4. 部署应用
部署应用之前需要创建一个 deployment。这里的 deployment 是一个 k8s 术语,不是指部署的名词。deployment 存在于 master 中,用于控制 master 对容器实例的安装进行调度,将其分散到各个 node 上。比如说新安装一个容器,然后查看哪个 node 还有空间可以安装,于是安装到那个 node 上。
除了安装的时候,deployment 还会在容器安装之后对实例进行监控。比如某个 node 的机器坏掉了,就立刻将使用该 node 的实例换到其他 node 上。这也是 k8s 优于之前的自动化安装的脚本的好处,它无法监控故障。
以下内容先看后面的之后再回来看
deployment用于管理一组 pods(pods 后面会讲)。deployment 内部会自动创建和控制 ReplicaSet。ReplicaSet 是用于保证一组完全相同(顾名思义)的 pods 的可用性的集合。可以确保任何时间都有指定数量的 pods 在运行。
4.1 kubectl 的部署操作
- 使用
kubectl create deployment <deployment-name> --image=<image-name>
- 创建完毕后可以使用
kubectl get deployments
查看 deployment
实际上,在使用 kubectl 的时候,kubectl 就是在使用上图中的 Kubenetes API。
4.2 proxy
可以创建一个 proxy,这个 proxy 会代理所有的服务。
- 新开启一个终端,然后运行
kubectl proxy
, 会看到其在 localhost 上运行了一个服务器,这个服务器已经和 cluster 连接,可以互相通信。此时打开这个服务器会看到 Kubenetes API,比如说 version。此时,如果想要查询 version,不止可以用kubectl version
,也可以打开httpl://localhost:8001/version
查询。 - proxy 会给每一个 pods 都创建一个 endpoint,可以在 proxy 服务器上访问。
5. 查看 pods 和 nodes
5.1 pods
- 在上一节中,创建了 deployment,创建的时候,K8s 创建了一个 pod 来托管应用实例。pod 在 K8s 中用来表示一组一个或多个容器,以及这些容器的共享资源包括(数据卷,网络,容器的启动信息)。
- pod 可以看做一个逻辑主机的建模,管理对应的应用。每个 pod 管理一组相互紧耦合的容器。比如一个 pod 里既有 node.js 的容器,也有给 node.js 存储数据的容器。同一个 pod 中的容器们共享 ip 和端口(就是说一个 pod 里的所有的容器的 IP 和端口是一样的)。
- pods 是 Kubernetes 的原子单元。当生成 depolyment 的时候,deployment 就会生成带有容器的 pod。每个 pod 都与调度其的 node 绑定,一直到 node 终止或删除。当 node 故障的时候,就会在其他 node 上生成相同的 pod。
5.2 nodes
- pod 运行在 node 上。每个 node 可以有多个 pods,由 master 管理,master 的自动管理会考虑到剩余资源。每个 node 上至少运行以下两个:
- kubelet,负责 master 和 node 的通信,作用为管理 pods 和容器。
- 容器运行时,如 Docker。
5.3 kubectl 故障排除
- 各种
kubectl get xxx
命令,除了前面的 nodes, deployments 以外,还可以 get pods。 - 各种
kubectl describe xxx
命令,比如 describe pods 就可以查看 pods 的具体情况,比如什么 ip 端口啊,什么容器啊之类的。也可以用于 nodes, deployments 等等 kubectl logs <POD_NAME>
查看该 pod 的日志,也就是这个 pod 的所有 STDOUT 内容。kubectl exec <POD_NAME> -- xxx
可以在容器中执行命令。如kubectl exec -it $POD_NAME -- bash
可以打开容器的 bash
6. 用 Service 来公开暴露应用
pods 具有生命周期。当一个 node 结束后,上面的 pods 也会消失。这时候 replicaset 胡自动创建新的 pods 来代替。假设有一个集群,由前端和后端构成,前端不应该关心后端的 pods 是怎么变化的,即使后端的 pods 消失或改变。也就是说 pods 应该具有唯一的 IP 地址。为了自动协调这些改变,Service 就诞生了。
6.1 Service 是什么
Service 是一个抽象概念。定义了 pods 的逻辑集(也就是一组互相有逻辑关系的 pods)和访问 pods 的协议(访问这个 Service 里的 pods 的协议)。Service 使得从属 pods 之间的松耦合成为可能(前面提到的前端后端松耦合)。Serive 里的一组 pod 通常由 LabelSelector 来标记。
尽管每一个 pod 都有一个唯一 IP 地址,但是如果没有 Service,这些 IP 地址并不会暴露在 cluster 外面。Service 使得应用接收流量成为可能(因为有 IP 地址了)。Service 本身可以通过改变 ServiceSpec 中的 type 来暴露:
- ClusterIP。默认 type,在 Cluster 内部 IP 上公开 Service。这样就只能在 Cluster 内部访问这个 Service。
- NodePort。
- 使用 NAT,使得 Service 暴露在所有被选中的 nodes 上的同一个端口上。 在 Cluster 外部可以使用
<NodeIP>:<NodePort>
来访问这个 Service。 - 是 ClusterIP 的超集(也就是说也可以在内部访问)。
- 使用 NAT,使得 Service 暴露在所有被选中的 nodes 上的同一个端口上。 在 Cluster 外部可以使用
- LoadBanlancer。
- 在当前云中创建一个外部负载均衡器(如果支持的话),并为这个 Service 分配一个外部的固定 IP 地址。
- 是 NodePort 的超集(也就是说也可以用 1,2 的方式访问这个 Service)。
- ExternalName。
- 通过返回带有 ExternalName 的 CNAME 记录,使用 ExternalName(由 Spec 中的 externalName 指定) 访问这个 Service。
- 不使用代理
- 需要 kube-dns 的 1.7 及以上版本。
6.2 Service 和 Label
- Service 在一组 pods(就是这个 Service 包含的那些 pods) 之间通信。
- Service 是一组抽象,允许 pods 死亡并在 K8s 中复制,并且不影响应用的功能。
- 在依赖的 pods 中(比如之前提到的前端后端),前后端是通过 Service 来发现和路由的。
那么 Service 是如何与对应的 pods 绑定的呢?那就要使用 Label 和 Selector 了。Label 用于标识 Service,Selector 用于选择这个 Service。
Label 是一个键值对,可以用于多种用途:
- 标识开发,测试,生产的对象。
- 标识版本
- 对象分类。
标签既可以在创建的时候被声明,也可以之后修改。一个对象可以有多个 label。
如下图,在 label 标识完毕后,比如有的 pod 的标签是 app:A,有的是 app:B,那么就可以用 app=A 这个 Selector 来选中 app:A 的一组 pods 了。
6.3 使用 Service 来暴露应用
- 首先使用
kubectl get services
来查看 services。由于我们从来没有创建过 service,控制台只显示有一个 minikube 自动创建的叫做 kubenetes 的 service。 - 于是使用 expose 命令来创建,
kubectl expose deployment/<deploymentName> --type="NodePort" --port=8080
,这里创建了一个 type 为 NodePort 的 service,端口是 8080。 - 创建后可以通过外部访问该 service。如果你还记得,要使用
<NodeIP>:<NodePort>
访问。 - 创建后使用 describe 查看可以发现这个 service 上已经有一个 label 了。
6.4 使用 label
- 使用
kubectl describe deployments
可以发现 deployment 上已经有一个 label 了。 - 在
kubectl get pods
时候使用 -l 可以使用 selector,如kubectl get pods -l app=xxx
- 也可以更改 label,如使用
kubectl label <object type> <object name/id> version=v1
,这里的 version 并不是什么专门的,而是自己随便写的单词,这里表示加上一个标签表示版本为 v1。
6.5 删除 service
删除 service 的时候也可以使用 -l 参数,指定删除包含某个标签的 service。
删除后再使用 curl <NodeIP>:<NodePort>
发现已经不能访问 pod 了,但是进入 pod 后可以看到应用依然在运行。
7. 应用的扩缩
之前创建 deployment 的时候,其绑定的只有一个 pod。而实际上当流量增加的时候,当然需要更多地 pods 来进行负载均衡。
顾名思义,扩展就是增加 pod,找到有可用资源的 node,将资源调度请求分配到新的 pod 上。收缩就是减少 pods 的数量。
但是 deployment 只是增加 pod,但是如何分配流量呢?这里就要靠 Service,Service 有一种负载均衡类型,可以将流量均衡分配到外部可访问的 pods 上。Service 会一直监视端口,保证流量只会分配到可用的 pods 上。
一旦应用有多个实例之后,就可以进行不宕机的应用更新了。
7.1 应用扩缩实战
7.1.1 ReplicaSet
- 之前提到过 deployment 会自动创建并控制 ReplicaSet。如果想看到的话,可以用 kubectl get rs 看到所有 ReplicaSet。
- 当使用
kubectl get deployments
的时候,其实那些数字指的都是 Replicas(不是 ReplicaSet 哦,是应当维护的副本的数量) 的数量。
7.1.2 扩容
- 扩容指的就是扩充 Replica 的数量(注意不是 ReplicaSet 的数量哦),比如说使用
kubectl scale deployments/xxx --replicas=4
,意思就是让给定的 deployment 的 Replicas 增加到 4 个。这样我们就有了 4 个 应用的实例。 - 这个时候查看 pods,会发现 pods 也改变了。
- 创建 services,并使用 curl 访问,会发现每次都访问到不同的 pods,证明确实有负载均衡。
7.1.3 收缩
- 其实和扩容命令一模一样,只是把 replicas 的数量改小,用的都是同一个命令:
kubectl scale deployments/xxx --replicas=2
8. 滚动更新
在实际生活中,开发人员需要经常更新应用,但是用户又持续需要使用应用。在 K8s 中,这是通过滚动式更新来完成的。
- 滚动式更新就是让新的实例逐步更换掉旧的实例,零停机地进行 deployment 的更新。
- 前面我们把应用分为多个实例,这其实也是滚动更新的一个要求。要有多个实例才能做到滚动更新。
- 在 K8s 中,更新是具有版本控制的。可以随时退回之前的版本。
Service 在更新的过程中只会对可用的 pods 进行负载均衡。
滚动更新可以做到以下操作:
- 将应用程序从一个环境提升到另一个环境,通过 image 更新。
- 回滚到以前的版本。
- 持续集成和交付应用,而无需宕机。
8.1. 更新应用
- 使用
kubectl set image deployments/xxx <container>=<container>:tag
来更新应用。更新过程可以看到如下图所示:
- 查看 Service,发现之前设置的端口没有任何变化。
- 使用
kubectl rollout status deployments/xxx
可以查看更新是否完成。 - 更新完成后,查看 pod 的详细信息,发现 image 确实更新了。
8.2 回滚应用
- 使用
kubectl rollout undo deployments/xxx
可以回到这次更新前的状态。