K8s笔记之二:在K8s集群里部署应用和服务

部署环境

在上一篇里,我已经在三台N1上组了一个集群,其中KUBESVR1是控制平面,SVR2和SVR3是节点。后来因为上了PVE,又把N1集群换下来了,现在这三台是放在PVE里跑的,实际使用没什么区别。总体性能差不多(并发N1可能还好一些,毕竟4核),J1900的耗电还大些。但用来学习已经可以了。

基本配置是这样的:

  • 2G RAM
  • 8G SSD
  • 1 or 2 Cores(考虑到J1900僝弱的性能,只给Control Plane双核,因为它至少需要双核,其它节点都是单核)

对于一个相对完整的高可用K8s环境,会需要上十台服务器。但如果只是简单部署个测试环境就简单得多了:

  • 一台Control Plane
  • 一台工作节点(这里用了两台,毕竟咱有这个条件)

应用部署

基于上述概念来定义一个最基本的应用部署大致是这样:

  • 先把应用打包成一个容器镜像
  • 再把这个镜像在k8s集群里跑起来,方式有两类:
    • 直接把镜像作为一个Pod运行起来
    • 通过配置文件运行Pod(Pod配置或Deployment配置)
  • 通过Pod的内部IP访问应用

现在就来实现这样一个部署吧。

直接部署

在所有节点都Ready的状态下,在Control Plane上运行:

kubectl run pyhttp --image=python:3.7-buster --command -- python -m http.server

即可在节点中启动一个pod,其中运行了一个python的web服务。其中pyhttp是这个pod的名字,--image参数指定了镜像的名字,--command --之后的部分是容器的运行命令。

查看pod的状态:

kubectl get pods

或者查看更详细的信息,特别是当Pod启动失败时,可以通过这个命令查看到错误原因:

kubectl describe pod pyhttp

python自带的这个httpserver默认是监听8000端口的,但问题是它的IP是什么呢?在k8s里,每个pod都会有一个虚拟的IP,这可以通过查看pod详细取得。如果pod运行在多个nodes上,还会有多个虚拟IP。所以我们现在可以这样访问:

curl http://<virtual_ip>:8000

如果我们现在不需要这个Pod,可以把它删除:

kubectl delete pod pyhttp --now

其它Pod操作:

# 查看容器日志
kubectl logs pod pyhttp
# 进入容器
kubectl exec -it pyhttp -- /bin/bash

通过配置文件部署

因为命令直接部署相对比较麻烦,特别是需要多次部署或自动化操作的情况,所以更好的方法是通过配置文件部署。

这也有两种方式,一个是直接配置Pod,更好的方法是配置Deployment。

直接配置Pod是这样,写一个pyhttppod.yml的配置文件:

apiVersion: v1
kind: Pod
metadata:
  name: pyhttppod
spec:
  containers:
    - name: pyhttppod
      image: python:3.7-buster
      command: ['python', '-m', 'http.server']

然后运行命令部署:

kubectl apply -f pyhttppod.yml

简单的Pod配置部署功能还是弱了一点,更好的方法是用Deployment方式pyhttpdeploy.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pyhttpdeploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pyhttp
  template:
    metadata:
      labels:
        app: pyhttp
    spec:
      containers:
        - name: pyhttpdeploy
          image: python:3.7-buster
          command: ['python', '-m', 'http.server']

改动了几个地方:

  • apiVersion改为apps/v1
  • kind由Pod改为Deployment
  • 增加了replicas,指定副本数量,在有多个节点服务器的集群里,Pod的多个副本可以运行在不同的节点上
  • 增加selector用于标签匹配,只有完全匹配标签的容器模板才会被部署
  • containers配置移到template下,即原来的容器配置变成容器模板配置,可以增加部署的灵活性

同样运行命令kubectl apply -f pyhttpdeploy.yml部署。

现在用kubectl get pods查看就可以看到这个Pod有两个实例:

pyhttpdeploy-5897d584b7-8dg6l   1/1     Running   0          52s
pyhttpdeploy-5897d584b7-v6fqc   1/1     Running   0          52s

查看其中一个kubectl describe pod pyhttpdeploy-5897d584b7-8dg6l取得IP,即可通过http://<virtual_ip>:8000访问页面。

Deployment的相关命令:

# 查看deployment
kubectl get deployments
# 删除deployment
kubectl delete deployment pyhttpdeploy
# 查看deployment版本历史
kubectl rollout history deployment pyhttpdeploy
# 回滚deployment到上一版本
kubectl rollout undo deployment pyhttpdeploy
# 回滚到指定版本
kubectl rollout undo deployment pyhttpdeploy --to-revision=1
# 副本数量调整
kubectl scale deployment pyhttpdeploy --replicas=3

通过服务配置文件部署

如前面所说,我们部署完Pod以后都需要先查看一下Pod的虚拟IP才能连接,这很不科学:

因为这个虚拟IP是动态分配的,如果Pod因为某些原因(比如节点服务器故障)导致被干掉,然后重启(可能在不同的节点上),它的IP必然会被重新分配,那么我们就连不上这个Pod了。

另外,如果我们部署了Pod的多个副本,但每个副本有独立的虚拟IP,所以客户端连哪个副本也是问题,除非自己在前面做一个负载均衡。

为了解决这个问题,K8s提供了服务的概念。

服务是Pod之上的一层抽象,相当于一个虚拟的负载均衡IP:当客户端访问这个服务虚拟IP时,会自动被映射到实际的Pod虚拟IP上——不论这个Pod是否被重新部署或者有多个副本,服务的虚拟IP都会自动更新到最新可用的Pod虚拟IP上。

注意:服务是相对Pod独立的应用,即需要有相应的Pod(实际是Deployment)配置才能使用服务部署,服务通过选择标签进行匹配(所以需要Deployment,Pod配置不带标签)。

创建一个服务pyhttpsvc.yml

apiVersion: v1
kind: Service
metadata:
  name: pyhttpsvc
spec:
  selector:
    app: pyhttp  # 匹配相应的Pod
  type: ClusterIP
  ports:
    - port: 8000 
      targetPort: 8000

kubectl apply -f pyhttpsvc.yml启动服务后,即可用kubectl get svc查看服务的虚拟IP:

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
...
pyhttpsvc    ClusterIP   10.104.83.181   <none>        8000/TCP   7m59s

然后就可以通过http://10.104.83.181:8000访问之前用Deployment部署的Pod了,不论它是否重启或有多个副本。

但问题在于,这个IP还是集群内的虚拟IP,从外部还是无法访问,虽然可以用kubectl port-forward service/pyhttpsvc 8000:8000把端口映射出去,但不推荐。

正确的做法是改用NodePort

apiVersion: v1
kind: Service
metadata:
  name: pyhttpsvc
spec:
  selector:
    app: pyhttp
  type: NodePort  # 改为NodePort
  ports:
    - port: 8000 
      targetPort: 8000
      nodePort: 30800  # 增加端口号,限30000-32767

这样就可以通过任一节点服务器的IP的30800端口访问到服务了。现在看kubectl get svc就会显示映射端口了:

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
...
pyhttpsvc    NodePort    10.104.83.181   <none>        8000:30800/TCP   38m

如果有很多节点服务器的话,这样其实仍然不太方便,毕竟任何一个节点都可能下线,对外用哪个IP都不合适,除非在前面再加一个负载均衡。

K8s其实有自带这么个LoadBalancer,跟NodePort一样,这也是一个服务类型,会创建一个虚拟的外网IP,自动映射到所有可用的节点IP。不过现在它已经被Ingress取代了,这里就不多说了。

推送到[go4pro.org]