k8s(六)走进Pod
- June 27, 2022
Pod - 容器的抽象与封装
作者:lomtom
个人网站:lomtom.cn 🔗
个人公众号:博思奥园 🔗
你的支持就是我最大的动力。
k8s系列:
引入
在 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,实际代码在文章末尾)
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的案例:
- 日志收集
- 后端服务与数据库(生产环境不建议,数据库建议单独使用)
当然,实际生产中去使用Pod不会如此简单,往往会搭配Secret、ConfigMap、PVC等等一起来使用,如果感兴趣的话,持续关注吧。
源代码
- 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(¶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源代码:
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
}
}