本篇我们开始用k8s的方式部署静态网站镜像,并通过这个过程了解k8s为什么会抽象出那么多概念。
1. Node
k8s首先需要选定在哪一台或哪几台服务器上部署。
如我们在kubeadm部署k8s集群的时候就已知的,k8s集群是由1-N台Master节点和N个Worker节点组成的。
k8s的设计原则之一就是不挑Worker节点的硬件配置。毕竟当初Google搭Borg集群的时候淘到的服务器硬件各式各样都有。(想起来当初搭Hadoop的时候也有人问过我是不是什么硬件都可以。。。以Hadoop MapReduce的吃硬盘和网络程度,实体机+专用万兆宽带跑出来的性能比虚拟机快出好几倍。当然配置低也的确能跑起来不死。)
你手头的机器可能是什么歪瓜裂枣都有:
不管是实体机还是虚拟机,不管什么硬件配置,不管高的矮的胖的瘦的,k8s都将其一视同仁地抽象为一个Node(曾经也有一个专有名词minion)。
一个典型的Node信息如下(部分删减):
1 | Name: docker-7 |
Master节点不断轮询更新每个Node的状态信息:“你还活着么?压力大不大?CPU/内存/存储已经分配了多少?还剩下多少?”
这样当Master节点为了新的容器需求征兵时,就能很容易地知道哪几个节点还够压榨。
2. Pod
Pod是Kubernetes中的原子调度单位。最简单的Pod就等于一个容器。这么表述也就意味着Pod里也可以放多个容器。
看为什么Kubernetes会发明出来Pod这个概念?因为有些容器之间有共享网络和存储的需求。
举个最典型的例子:日志收集器。例如我们的静态网站容器会在被访问的时候生成access.log文件。如果是在虚机里部署,那么就会在服务器上另外起一个logstash,收集这些日志文件后汇总到Elasticsearch。
如果logstash和Nginx不在同一台宿主机上部署,虽然也不是不可行,但就会很折腾:后文会提到挂载PV,可以在logstash和Nginx的Pod上都挂载同一个PV。但这个折腾毫无必要。日志文件并不那么重要,不值得永久存储,生命周期跟着容器即可。当容器被销毁时,日志也可以跟着被销毁。
那么我们是不是可以把Nginx和logstash打包到同一个容器中呢?这会产生一个问题:当logstash进程挂掉时,k8s的监控怎么表示容器的状态?如果显示Failure然后重启容器,则无辜的Nginx进程也受到了牵连;如果显示正常,那么要单独重启容器里的某一个进程就会变得很麻烦。
所以我们需要将Nginx和logstash单独打包为容器,部署成一个Pod。k8s会将一个Pod里的容器都部署在同一台宿主机上。
k8s的官方博客将多容器Pod的类型列为三种:
- Sidecar(边车)容器
- Ambassador(大使)容器
- Adapter(适配器)容器
k8s Pod的实现方式特别适合我们将一些控制平面的功能放到Sidecar中。近几年很知名的Service Mesh项目就是完全通过Sidecar模式支撑起来的。我们以后再详细讨论这个话题。
其实对于Pod不用想得太复杂,可以认为它就是逻辑上的一台虚机的概念。只有必须部署在同一台虚机上的,才会被并到一个Pod里。
例如虽然MySQL和Java应用虽然也可以部署在同一台虚机上,但从best practice考量,一般不会这么部署,所以就不是一个Pod。MySQL的Master和Slave一般是部署在两台服务器上,所以也是两个独立的Pod。
2.1 Pod YAML
要部署一个Pod,我们需要先写一个YAML描述文件,然后用k8s的命令部署。
我从Spring Boot的配置开始就已经接触了挺久的YAML,所以这边就不详细介绍YAML的语法了。如果有对语法部署的可以参考Wiki。
一个最简单的Nginx Pod部署YAML如下:
1 | apiVersion: v1 |
这个YAML第一层的元素有四个:apiVersion, kind, metadata和spec。
apiVersion的v1表示是稳定版。(像之前kubeadm的apiVersion就是kubeadm.k8s.io/v1beta1,还在快速迭代中)。
kind就是想要创建的对象的类型。
metadata是用来识别对象的唯一性。
spec字段是对象规约,内容随着每个Kubernetes对象而不同。对于该Nginx Pod来说,需要指定使用的唯一镜像以及镜像的版本。关于镜像和镜像版本可以在Docker Hub上查到。
2.2 部署命令
我们可以使用如下的命令部署:
1 | kubectl apply -f nginx-pod.yaml |
然后就可以通过如下命令查看Pod创建进展:
1 | kubectl get pod |
以及通过如下命令查看Pod详细信息:
1 | kubectl describe pod <Pod名> |
想进入Pod,就是用exec -it命令:
1 | kubectl exec -it nginx-pod /bin/bash |
如果不需要这个Pod了,可以用以下命令取消部署:
1 | kubectl delete -f nginx-pod.yaml |
k8s的命令虽然很多,但都非常有规律。基本格式都是:
1 | kubectl 动作 对象类型 |
或
1 | kubectl 动作 [-参数] 对象类型 对象名 |
对于非default namespace的对象,再加一个namespace参数:
1 | kubectl 动作 对象类型 对象名 -n namespace名 |
基本不怎么需要特别记忆。
另外提一下,在使用了k8s后,最好不要再打Docker命令了。当然自己本地开发机上跑容器的时候还是需要拍Docker命令。
在本次POC中,我们不将Nginx部署为Pod。原因在下一章中说明。
2.3 验证
此时Pod已部署成功,但我们还没法从容器外部访问。想验证的话可以稍微修改一下YAML,增加一个验证的shell容器,然后共享PID Namespace:
1 | apiVersion: v1 |
上述配置在spec下增加了shareProcessNamespace: true,表示PID Namespace共享。最底下还增加一个镜像为busybox的容器。(busybox和alpine由于体积比较小,在k8s部署的时候有广泛的用途)
要部署之前,我们需要先kubectl delete -f nginx-pod.yaml把pod删除,不然会得到告警:
1 | spec.containers: Forbidden: pod updates may not add or remove containers |
在重新apply后使用如下的命令进入Pod
1 | kubectl attach -it nginx-pod -c shell |
进入busybox容器后执行ps aux,就可以看到Nginx的进程了:
1 | / # ps aux |
2.4 infra容器:pause
上一节进程的查询结果中有一个pause进程需要说明一下。
当我们使用docker ps查看容器时,会发现有相当多的pause容器在启动。该容器和我们前几节提到的多容器Pod有关。
以我们上面修改后的nginx-pod范例为例。当我们需要让两个容器共享Namespace(不仅仅是IPC,还包括Network等其他Namespace),方法之一是让一个容器先启动,然后将另外一个容器的Namespace设置为前一个容器的Namespace。从技术上可行,但这个会导致这两个容器之间的关系不再是对等拓扑关系。
所以需要有个中间容器,也就是pause容器存在。pause容器非常小,也基本干不了什么事。它唯一的作用就是在Pod启动的最开始就启动,然后在其他容器启动后和它们共享自己的Namespace。在所有的容器启动完成后就暂停自己,不再消耗资源。
3. Deployment
Pod的副本实例数是在Deployment中定义的。
假设我们从负载和高可用的角度考虑,想要在k8s中部署2个Nginx的实例,那么只需要在Deployment的配置中定义replicas: 2即可。
我们实际写k8s yaml配置的时候,不推荐直接部署为Pod。即使是只有一个副本,也推荐部署为replicas为1的Deployment。这是由于k8s的调度机制是通过Deployment来确保副本数量。万一Pod所在的服务器挂了,k8s会检测到副本数不足1,于是将Pod调度到健康的Node上。
3.1 Deployment YAML
一个Nginx Deployment的精简版的YAML如下:
1 | apiVersion: apps/v1 |
可以看到spec下有一个replicas,表述副本数量。
看到这个YAML,我第一反应是有些晕:为什么spec套了一层spec,template下还有一层metadata。
这篇matchLabels, labels, and selectors explained in detail, for beginners解释了为什么这个YAML有点绕的原因。关键点是这个Deployment YAML中的template其实是podTemplate。这样你就会发现template中的部分只要加上apiVersion和kind,基本就是Pod的YAML。
与template平级的还有一个selector选择器属性。这个selector选择器表示deployment部署的是label里带app的podTemplate。
label作为一个非唯一的标签属性,可以使Kubernetes的运维更加灵活。这个我们后续再详细研究。
我们现在可以总结出一个最简单版的deployment yaml模板:
1 | apiVersion: apps/v1 |
3.2 部署命令
部署命令和Pod基本一样:
1 | kubectl apply -f nginx-deployment.yaml |
然后查看到ready的deployment了:
1 | [root@docker-4 pod]# kubectl get deployment |
3.3 部署POC Nginx镜像
要部署POC的Nginx镜像,只需要将image修改为私有仓库的镜像即可:
1 | apiVersion: apps/v1 |
如果使用Docker Hub作为Docker Registry,则首先创建secret(什么是secret会在容器-12-Kubernetes实战-静态网站部署优化1:ConfigMap,Secret与TLS | Galaxy 中详细说明):
1 | kubectl create secret docker-registry regcred --docker-username=账号 --docker-password=密码 --docker-email=Docker邮箱 -n 命名空间默认default |
然后在pod定义的同一级加上imagePullSecrets,例如:
1 | apiVersion: apps/v1 |
4. 参考资料
Kubernetes Node的官方文档概念介绍
Nodes - Kubernetes
Kubernetes Pod的官方文档概念介绍
Pod Overview - Kubernetes
Kubernetes Deployment的官方文档概念介绍
Deployments - Kubernetes
这是一篇关于Pause容器的详细介绍
The Almighty Pause Container - Ian Lewis
本篇部分有趣的图来自这篇。很简要易懂的科普文。
Kubernetes & Traefik 101— When Simplicity Matters – Gérald Croës – Medium
如果想补习YAML的话可以参考这篇:
YAML basics in Kubernetes – IBM Developer