作者:lomtom
个人网站:lomtom.cn
个人公众号:博思奥园
你的支持就是我最大的动力。
k8s系列:
k8s(一)走进docker
k8s(二)Docker的HelloWorld
k8s(三)走进k8s
k8s(四)安装k8s集群
k8s(五)k8s的HelloWorld
k8s(六)走进Pod
引入 在 k8s(三)走进k8s 中有提到Pod的概念,Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元 ,它通常由一个或者多个容器组成。
不要一听到这个概念就觉得他很抽象难理解,其实Pod 这个看似复杂的 API 对象,实际上就是对容器的进一步抽象和封装而已。
那么在k8s中为什么会有Pod这个对象呢?
前面的章节有说过,容器在我们的操作系统中本质生就是一个进程,然后通过Namespace 、Cgroups、rootfs等技术来实现隔离、资源限制等。但是我们知道,在操作系统中,往往进程是以进程组的形式存在的。所以k8s将“进程组”的概念引入。
Pod中的多个业务相关的容器共享网络及存储可以简化密切关联的业务容器之间的通信问题及文件共享问题。
在Pod中创建多个容器 在k8s(五)k8s的HelloWorld 其实也创建了一个Pod,但是这个Pod中实际上只有一个容器。是因为使用Deployment控制器时,模板中只指定了一个容器。
那么怎么在一个Pod中指定多个容器呢?
可以看到spc.containers
实际上是一个数组,我们可以为每一个容器设置相应的参数。
例如,这里将在一个名叫test-pod
的Pod中创建两个容器,分别为container1、container2,两个容器共享主机(Pod运行的机器)下的/data
目录,并且都将该目录挂载在自己的/tmp
目录下,这样就可以分别通过两个容器访问同一个文件夹及文件夹下的文件。
container1的作用是通过8080端口向/tmp
目录下写入文件,container2的作用是通过8081端口读取同一个文件。(镜像的构建就不多做赘述了,可以参考k8s(二)Docker的HelloWorld ,实际代码在文章末尾)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 apiVersion: v1 kind: Pod metadata: name: test-pod labels: name: test-pod spec: restartPolicy: Never volumes: - name: shared-data hostPath: path: /data containers: - name: container1 image: lomtom/test-pod volumeMounts: - name: shared-data mountPath: /tmp - name: container2 image: lomtom/test-pod2 volumeMounts: - name: shared-data mountPath: /tmp
创建好Pod之后,可以使用kubectl命令查看创建好的Pod,该Pod的READY信息为2/2,并且STATUS为Running代表两个容器都已经成功启动。
1 2 3 [root @master service ] NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE test-pod 2 /2 Running 0 37 m 10.244 .1.254 slaver <none>
使用kubectl describe pod test-pod-c7fc49694-2sgsz
查看Pod信息,可以看到,该Pod实际上是由两个容器组成,并且两个容器同时挂载了名字为shared-data
的存储卷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 [root @master ~] Name: test-pod -c7fc49694 -2sgsz Namespace: default Priority: 0 Node: slaver/8.16 .0.101 Start Time: Wed, 11 May 2022 16 :58 :26 +0800 Labels: app=test-pod pod-template -hash =c7fc49694 Annotations: <none> Status: Running IP: 10.244 .1.221 IPs: IP: 10.244 .1.221 Controlled By: ReplicaSet/test-pod -c7fc49694 Containers: container1: Container ID: docker://4 a7240671561e7e94103ecff56bde8a29f84c1f3080803e14e9383b62d6ac648 Image: lomtom/test-pod Image ID: docker-pullable ://lomtom/test-pod @sha256:36 e63b22f860ccedde7196a9200d29e05422ea4ffc2231495e4ebc5a1f5f038f Port: <none> Host Port: <none> State: Running Started: Mon, 20 Jun 2022 15 :26 :20 +0800 Last State: Terminated Reason: Error Exit Code: 255 Started: Wed, 11 May 2022 17 :02 :04 +0800 Finished: Mon, 20 Jun 2022 15 :01 :00 +0800 Ready: True Restart Count: 1 Environment: <none> Mounts: /tmp from shared-data (rw) /var/run/secrets/kubernetes.io/serviceaccount from default-token -q69ll (ro) container2: Container ID: docker://c482ccf9e4c95b0db0e4b02fc146cc9312f2402009d249d12da5bbcb5e5a6694 Image: lomtom/test-pod2 Image ID: docker-pullable ://lomtom/test-pod2 @sha256:aab379c406508a9405fde235a07883187140abd299e5e0cffecfa79d01dc12a7 Port: <none> Host Port: <none> State: Running Started: Mon, 20 Jun 2022 15 :27 :30 +0800 Last State: Terminated Reason: Error Exit Code: 255 Started: Wed, 11 May 2022 16 :59 :54 +0800 Finished: Mon, 20 Jun 2022 15 :01 :00 +0800 Ready: True Restart Count: 1 Environment: <none> Mounts: /tmp from shared-data (rw) /var/run/secrets/kubernetes.io/serviceaccount from default-token -q69ll (ro) ...... Volumes: shared-data : Type : HostPath (bare host directory volume) Path: /data HostPathType: ......
为该Pod创建Service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: v1 kind: Service metadata: name: test-pod labels: name: test-pod spec: type: NodePort ports: - port: 8080 name: container1 nodePort: 30080 - port: 8081 name: container2 nodePort: 30081 selector: name: test-pod
通过service的ClusterIP + Port访问Pod的容器(或者Matser IP + NodePort、或者Pod IP + 容器端口):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root @master service ] service/test-pod created [root @master service ] NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96 .0.1 <none> 443 /TCP 61 d test-pod NodePort 10.102 .156.46 <none> 8080 :30080 /TCP,8081 :30081 /TCP 49 s[root @master service ] "" [root @master service ] "ok" [root @master service ] "123"
那么怎么验证这个文件确实存在与主机上呢?在该pod运行的机器上查看/data目录即可。
1 2 3 4 [root @slaver ~] config [root @slaver ~] 123
通过以上实例,我们确实实现了不同的容器在同一个Pod中共享文件系统的案例。但是Pod的好处远远不止于此:
属于同一个Pod的多个容器应用之间相互访问时仅需要通过localhost 就可以通信,可以在pod1中通过localhost:8081访问pod2。
Pod除了主要的容器(这里为container1、container2)之外,还存在一个Infra 容器(通常大小为100-200k),镜像为k8s.gcr.io/pause
,以次容器状态代表整个Pod的状态,且Pod的生命周期与Infra 容器一样。
同一个Pod下的所有的容器共享该Pod的所有网络资源
多个容器在同一个Pod的案例:
日志收集
后端服务与数据库(生产环境不建议,数据库建议单独使用)
当然,实际生产中去使用Pod不会如此简单,往往会搭配Secret、ConfigMap、PVC等等一起来使用,如果感兴趣的话,持续关注吧。
源代码
test-pod源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package mainimport ( "github.com/gin-gonic/gin" "log" "os" ) type Param struct { Content string `form:"content"` } func main () { r := gin.New() r.GET("" , func (context *gin.Context) { open, err := os.OpenFile("/tmp/gin/config" , os.O_WRONLY|os.O_TRUNC, 0600 ) if err != nil { log.Println("open err " , err) return } defer func (open *os.File) { err = open.Close() if err != nil { log.Println(" close file err:" , err) } }(open) var param Param err = context.ShouldBindQuery(¶m) if err != nil { context.JSON(400 , err) return } _, err = open.Write([]byte (param.Content)) if err != nil { log.Println("change file err:" , err) return } context.JSON(200 , "ok" ) return }) err := r.Run(":8080" ) if err != nil { return } } func init () { _ = os.MkdirAll("/tmp/gin" , 0755 ) _, err := os.Create("/tmp/gin/config" ) if err != nil { panic (err) return } }
test-pod2源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "github.com/gin-gonic/gin" "os" ) func main () { r := gin.New() r.GET("" , func (context *gin.Context) { file, err := os.ReadFile("/tmp/gin/config" ) if err != nil { context.JSON(200 , "ok" ) return } context.JSON(200 , string (file)) return }) err := r.Run(":8081" ) if err != nil { return } }