k8s(六)走进Pod

k8s(六)走进Pod

Pod - 容器的抽象与封装

作者:lomtom

个人网站:lomtom.cn 🔗

个人公众号:博思奥园 🔗

你的支持就是我最大的动力。

k8s系列:

  1. k8s(一)走进docker
  2. k8s(二)Docker的HelloWorld
  3. k8s(三)走进k8s
  4. k8s(四)安装k8s集群
  5. k8s(五)k8s的HelloWorld
  6. 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目录下,这样就可以分别通过两个容器访问同一个文件夹及文件夹下的文件。

image-20220621105934716

container1的作用是通过8080端口向/tmp目录下写入文件,container2的作用是通过8081端口读取同一个文件。(镜像的构建就不多做赘述了,可以参考k8s(二)Docker的HelloWorld,实际代码在文章末尾)

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代表两个容器都已经成功启动。

[root@master service]# kubectl get pod -o wide
NAME                     READY   STATUS      RESTARTS   AGE   IP             NODE     NOMINATED NODE   
test-pod                 2/2     Running     0          37m   10.244.1.254   slaver   <none>          

使用kubectl describe pod test-pod-c7fc49694-2sgsz查看Pod信息,可以看到,该Pod实际上是由两个容器组成,并且两个容器同时挂载了名字为shared-data的存储卷。

[root@master ~]# kubectl describe pod test-pod-c7fc49694-2sgsz
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://4a7240671561e7e94103ecff56bde8a29f84c1f3080803e14e9383b62d6ac648
    Image:          lomtom/test-pod
    Image ID:       docker-pullable://lomtom/test-pod@sha256:36e63b22f860ccedde7196a9200d29e05422ea4ffc2231495e4ebc5a1f5f038f
    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:

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 + 容器端口):

[root@master service]# kubectl create -f demo-gin.yaml 
service/test-pod created

[root@master service]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP                         61d
test-pod     NodePort    10.102.156.46   <none>        8080:30080/TCP,8081:30081/TCP   49s

[root@master service]# curl http://10.102.156.46:8081
""
[root@master service]# curl http://10.102.156.46:8080?content=123
"ok"
[root@master service]# curl http://10.102.156.46:8081
"123"

那么怎么验证这个文件确实存在与主机上呢?在该pod运行的机器上查看/data目录即可。

[root@slaver ~]# ls /data/gin/
config
[root@slaver ~]# cat /data/gin/config 
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的案例:

  1. 日志收集
  2. 后端服务与数据库(生产环境不建议,数据库建议单独使用)

当然,实际生产中去使用Pod不会如此简单,往往会搭配Secret、ConfigMap、PVC等等一起来使用,如果感兴趣的话,持续关注吧。

源代码

  1. test-pod源代码:
package main

import (
	"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(&param)
		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
	}
}
  1. test-pod2源代码:
package main

import (
   "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
   }
}
lomtom

标题:k8s(六)走进Pod

作者:lomtom

链接:https://lomtom.cn/a6f32258