注: 本文采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。

通过Kubernetes网络篇——Pod网络(上)一文,我们已经对Pod以及Pause容器有了基本的认识。本文,我们将通过对Pod的部署实验,进一步认识Kubernetes的Pod网络。

部署第一个Pod

准备工作

下面我们通过一个Pod的部署,来看一下Kubernetes在为Pod建立网络的过程中还有哪些值得注意的地方。为了方便后面做实验,我们在master节点的目录/root下新建一个文件夹:

$ mkdir test-k8s-net
$ cd test-k8s-net/

然后在这个目录下,把下列Deployment定义存成文件,取名test-pod-1.yaml

$ cat > test-pod-1.yaml <<"EOF"
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-pod
  labels:
    app: lab-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lab-web
  template:
    metadata:
      labels:
        app: lab-web
    spec:
      containers:
      - name: lab-web
        image: mr.io/lab-web
        ports:
        - containerPort: 80
          name: web-port
EOF

这里用于测试的镜像mr.io/lab-web,就是我们在Kubernetes网络篇——将CNI用于容器一文里用过的镜像morningspace/lab-web。只是为了方便在本地做测试的时候,能够让Kubernetes集群里的节点快速拉取镜像,我们把镜像在本地构建出来以后,推送到了本地的私有Docker注册表mr.io上:

$ docker build -t mr.io/lab-web .
$ docker push mr.io/lab-web 

有关kubeadm-dind-cluster如何结合私有Docker注册表的使用方法,可以参考Kuberntes系列的热身篇:Launch multi-node Kubernetes cluster locally in one minute, and more…一文,以及位于GitHub上的lab-kubernetes项目。

部署Pod

接下来,我们调用kubectl命令把test-pod部署到集群里:

$ kubectl create -f test-pod-1.yaml
deployment.apps/test-pod created

查看default名字空间下的Pod清单,可以看到test-pod已经被成功部署到了节点kube-node-2上,对应的IP地址为10.244.3.3

$ kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE          NOMINATED NODE   READINESS GATES
test-pod-6575bc889-hw6bx   1/1     Running   0          31s   10.244.3.3   kube-node-2   <none>           <none>

验证结果

Kubernetes网络篇——认识CNI一文里,我们提到过,CNI在使用host-local作为IPAM插件时,会把IP地址的配置信息保存在/var/lib/cni/目录下。前面我们已经看到,kubeadm-dind-cluster用的也是host-local,所以我们可以“登录”到Pod所在的节点:

$ docker exec -it kube-node-2 bash

看一下这个目录:

$ ls /var/lib/cni/networks
dindnet

这里的dindnet是kubeadm-dind-cluster为整个Kubernetes集群网络定义的名称,来看一下这个子目录下都有哪些文件:

$ ls /var/lib/cni/networks/dindnet
10.244.3.3  last_reserved_ip.0	lock

看过Kubernetes网络篇——认识CNI一文的同学对这个应该已经很熟悉了。这里的10.244.3.3文件对应的是CNI插件为test-pod分配的IP地址,这和我们前面用kubectl get pods得到的结果是一致的。而这个文件包含的内容,则对应于test-pod的pause容器:

$ cat /var/lib/cni/networks/dindnet/10.244.3.3
a5f99e13e6adefc620d7af17a559a6f51d584e3d6b4a4703728ebfe48de91a33

docker ps查看kube-node-2上所有和test-pod相关的容器:

$ docker ps | grep test-pod
b3f451ace2c4        mr.io/lab-web          "nginx -g 'daemon of…"   3 minutes ago       Up 3 minutes                            k8s_lab-web_test-pod-6575bc889-hw6bx_default_d4d37088-9412-11e9-882b-2e5245ea0fee_0
a5f99e13e6ad        k8s.gcr.io/pause:3.1   "/pause"                 3 minutes ago       Up 3 minutes                            k8s_POD_test-pod-6575bc889-hw6bx_default_d4d37088-9412-11e9-882b-2e5245ea0fee_0

可以看到,pause容器的Id值a5f99e13e6ad,正是10.244.3.3文件里Id值的前几位。

再来看一下test-pod里的另一个容器lab-web。通过docker ps的输出结果,我们可以找到它的容器Id,然后再在这个容器里执行ip addr show命令:

$ docker exec b3f451ace2c4 ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1
    link/ipip 0.0.0.0 brd 0.0.0.0
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1
    link/tunnel6 :: brd ::
5: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 82:ba:d8:36:03:de brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.3.3/24 scope global eth0
       valid_lft forever preferred_lft forever

可以看到,这里的eth0对应的IP地址为10.244.3.3,实际上就是pause容器共享给lab-web的IP地址,也是整个test-pod的IP地址。

然后,我们再来看一下作为宿主机kube-node-2那一端的网络接口:

$ ip addr show
... ...
4: dind0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 1a:e1:4a:42:8f:c2 brd ff:ff:ff:ff:ff:ff
    inet 10.244.3.1/24 scope global dind0
       valid_lft forever preferred_lft forever
9: veth0a9314e9@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master dind0 state UP group default 
    link/ether 5a:83:e2:5b:8c:ca brd ff:ff:ff:ff:ff:ff link-netnsid 1
... ...

除了之前已经创建的dind0,还有一个新的接口veth0a9314e9。作为veth pair在宿主机一端的网络接口,它的序号是9。这和我们在lab-web容器里看到的接口eth0,其@符号后面跟着的序号是一致的。同样地,lab-web容器里eth0前面的序号是5,这和veth0a9314e9的@符号后面跟着的序号也是一致的。

再看一下容器lab-web的路由,也就是Pod的路由:

$ docker exec b3f451ace2c4 ip route show
default via 10.244.3.1 dev eth0 
10.244.3.0/24 dev eth0 proto kernel scope link src 10.244.3.3

可以看到,所有来自Pod的数据包,即IP地址为10.244.3.3,都将发送到网段10.244.3.0/24。其他的数据包则会和默认路由相匹配,通过eth0发送给网关10.244.3.1,也就是位于宿主机上的dind0。这说明,veth pair在宿主机一端已经成功连接到dind0了。

最后,我们的lab-web容器实际上是一个对外暴露80端口的nginx。在宿主机上执行curl命令,通过10.244.3.3向test-pod发送HTTP请求:

$ curl http://10.244.3.3
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
... ...

可以看到,位于test-pod里的lab-web容器是能够成功对外提供服务的。下图给出了test-pod的网络拓扑:

给Pod增加一个容器

使用相同端口

接下来,我们再给test-pod增加一个容器,修改test-pod-1.yaml文件如下,并另存为test-pod-2.yaml

$ vi test-pod-2.yaml 
$ cat test-pod-2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-pod
  labels:
    app: lab-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lab-web
  template:
    metadata:
      labels:
        app: lab-web
    spec:
      containers:
      - name: lab-web
        image: mr.io/lab-web
        ports:
        - containerPort: 80
          name: web-port
      - name: lab-web-2
        image: mr.io/lab-web
        ports:
        - containerPort: 80
          name: web-2-port

这里,我们尝试为test-pod增加一个新的容器:lab-web-2,使用的镜像同样是mr.io/lab-web,并且保持默认端口号80不变。然后执行kubectl apply命令重新部署test-pod:

$ kubectl apply -f test-pod-2.yaml
deployment.apps/test-pod configured

可是,当我们查看Pod状态的时候,发现test-pod处于Error状态,两个容器中只有一个启动成功了:

$ kubectl get pods
NAME                        READY   STATUS   RESTARTS   AGE
test-pod-78f5d68d7b-r8mbd   1/2     Error    1          19s

查看容器lab-web-2的日志可以看到,由于端口号80被占用,lab-web-2里的nginx并没有成功启动起来:

$ kubectl logs test-pod-78f5d68d7b-r8mbd -c lab-web-2
2019/06/22 00:22:36 [emerg] 1#1: bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
2019/06/22 00:22:36 [emerg] 1#1: bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
2019/06/22 00:22:36 [emerg] 1#1: still could not bind()
nginx: [emerg] still could not bind()

这就进一步证明了,在一个Pod里的多个容器是彼此共享同一网络的。它们就好像一台物理机上的多个应用,端口被其中一个应用占用以后,其他应用就不能再使用同一端口了。

使用不同端口

我们对test-pod-2.yaml稍作修改,并另存为test-pod-3.yaml

$ vi test-pod-3.yaml 
$ cat test-pod-3.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-pod
  labels:
    app: lab-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lab-web
  template:
    metadata:
      labels:
        app: lab-web
    spec:
      containers:
      - name: lab-web
        image: mr.io/lab-web
        ports:
        - containerPort: 80
          name: web-port
      - name: lab-web-2
        image: mr.io/lab-tomcat
        ports:
        - containerPort: 8080
          name: web-2-port

这里,我们把lab-web-2的镜像换成了mr.io/lab-tomcat,实际上就是tomcat在Docker Hub上的官方镜像,使用8080端口。和mr.io/lab-web一样,我们把它下载到本地后重新打上标签,并推送到私有Docker注册表mr.io

$ docker tag tomcat mr.io/lab-tomcat
$ docker push mr.io/lab-tomcat 

再次部署test-pod:

$ kubectl apply -f test-pod-3.yaml
deployment.apps/test-pod configured

查看Pod的状态可以看到,这一回test-pod部署成功了:

$ kubectl get pods -o wide                                                                   
NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE          NOMINATED NODE   READINESS GATES
test-pod-747778fb68-pfhjw   2/2     Running   0          59m   10.244.2.6   kube-node-1   <none>           <none>

有没有注意到,这次test-pod被部署到了kube-node-1上。这说明Kubernetes在选择部署Pod的节点时完全是动态的。现在,让我们进入到test-pod的lab-web-2容器里。这次我们使用的是kubectl exec命令。这样,就不必先登录到kube-node-1节点后再进入容器了,我们在master节点就可以直接进入容器:

$ kubectl exec -it test-pod-747778fb68-pfhjw -c lab-web-2 bash

然后在容器里执行curl命令,访问localhost的端口8080:

$ curl http://localhost:8080
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Apache Tomcat/8.5.42</title>
        <link href="favicon.ico" rel="icon" type="image/x-icon" />
        <link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <link href="tomcat.css" rel="stylesheet" type="text/css" />
    </head>
... ...

从返回结果可以看出,我们访问的是当前容器的tomcat服务器。如果,访问localhost的HTTP默认端口80:

$ curl http://localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
... ...

则返回的是来自nginx的响应,这说明我们访问的是nginx服务器。虽然nginx没有部署在当前容器里,但是由于它和tomcat是共享同一网络的,所以就好像部署在同一台物理机上一样,因此我们可以用localhost访问到它。下图给出了test-pod新的网络拓扑:

小结

对Pod网络的理解,关键在于pause容器。Kubernetes在为Pod分配资源时,会为每个Pod启动一个pause容器,并通过CNI插件为其配置好网络环境。然后再创建其他应用容器,并把它们加入到pause所提供的网络里。Pod的网络实际上是由pause容器来提供的,即便Pod里有某个容器崩溃了,只要pause还在,Pod的网络就不会受到影响。

留下评论

您的电子邮箱地址并不会被展示。请填写标记为必须的字段。 *

正在加载...