实例讲解kubernetes网络通信

本文以一个实例讲解 POD 间是如何通信的,后台服务是以 Flask,数据库 Redis,讲解 Redis 和 Flask 在不同的 POD 和不同的 Namespace 时是怎么通信的。

在本场 Chat 中,会讲到如下内容:

  • 在相同 Namespace 中 POD 之间是怎么通信的
  • 在不同的 Namespace 中 POD 之间是怎么通信的

初次写 GitChat,有不足的地方请大家指正。

我们如果创建了一些 pod,那么它们之间是怎么通信的呢?因为 pod 的 ip 地址是有可能变化的,这里我们主要讨论以下两个场景

  1. 同一网络下的不同 pod 间是怎么通信的?
  2. 不同的网络下不同的 pod 是怎么通信的?

一、同一网络下的不同 pod 间通信

第一种场景可能是应用最多的场景,比如我写了一个 web 应用,它使用 python 作为后端,使用 redis 作为数据库,redis 和 python 分别创建在不同的 pod 里,我会使用 deployment 创建 rs 的方式再创建 pod,正常情况下,我们是不希望这个 redis 被外面的应用访问到的,只允许在 python 的应用访问到,

架构

如上图,用户可以使用 python 应用暴露出来的 6000 端口(其实是 k8s 里的 service 暴露出来的)来访问应用,但是并不能直接访问里面 redis 的 6379 端口。

1.1 创建 redis pod

我们先创建一个 redis pod

apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: redis  name: redis-masterspec:  selector:    matchLabels:      app: redis  replicas: 1  template:    metadata:      labels:        app: redis    spec:      containers:      - image: redis        name: redis-master2        ports:        - containerPort: 6379

查看 pod 详细信息

# kubectl create -f r-deployment.yamldeployment.apps/redis-master createdkubectl get pods -o wideNAME                            READY   STATUS    RESTARTS   AGE   IP          NODE             NOMINATED NODE   READINESS GATESredis-master-7f88b489b9-k4c58   1/1     Running   0          27s   10.1.0.59   docker-desktop   <none>           <none>

1.2 创建 python 应用

我先使用 docker run 本地启一个 redis 用于代码调试,为了和上面启的 redis pod 区分(其实也不用区分,上面的 redis pod 本身也没有对外暴露端口),这里使用 6380 作为对外端口

docker run --name myredistest -d -p 6380:6379 redis

之后就可以使用 redis 客户端进行访问了,我在 db1 中创建了一个 redistest 的 key

扫描二维码关注公众号,回复: 9161456 查看本文章

写一个 python 应用,读取 redis 中的数据

#-*- coding:utf-8 -*-# author:Yang# datetime:2020/2/10 16:07# software: PyCharmfrom flask import Flaskfrom flask_redis import FlaskRedisimport timeREDIS_URL = "redis://{}:{}/{}".format('127.0.0.1', 6380, 1)app = Flask(__name__)app.config['REDIS_URL'] = REDIS_URLredis_client = FlaskRedis(app)@app.route("/")def index_handle():    redis_client.set("reidstest",time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time())))    name = redis_client.get("reidstest").decode()    return "hello %s"% nameapp.run(host='0.0.0.0', port=6000, debug=True)

之后用浏览器访问 127.0.0.1:6000 就可以得到正常的输出了

在这里插入图片描述

1.3 在 pod 中访问 redis

上面只是将 python 访问本地的 redis,我们最终是要将这个 python 应用打包成镜像,放到 k8s 中,那么如果在 k8s 中这个 flask 应用该如果访问到 redis 呢?

为了实现一套代码可以在不同的环境中执行,我在 redis 的初始化时加上一点判断

if os.environ.get("envname") == "k8s": # 说明是在 k8s 中    REDIS_URL = "redis://{}:{}/{}".format('redisIP', "redispord", 1)else:    REDIS_URL = "redis://{}:{}/{}".format('127.0.0.1', 6380, 1)

现在主要的问题在于,REDIS_URL = "redis://{}:{}/{}".format('redisIP', "redispord", 1) k8s 中的 redisIP 和 redispord 这里该填写什么呢?

上面使用kubectl get pods -o wide 查看到 redis 的 ip 为10.1.0.59 ,那么我们试试能不能通过这个 IP 和端口来访问 redis 呢?

if os.environ.get("envname") == "k8s": # 说明是在 k8s 中    REDIS_URL = "redis://{}:{}/{}".format('10.1.0.59', 6379, 1)else:    REDIS_URL = "redis://{}:{}/{}".format('127.0.0.1', 6380, 1)

先创建一个 Dockerfile

# Use an official Python runtime as a parent imageFrom python:3.5.7# Set the working directory to /appWORKDIR /app# ADD requirements.txtCOPY requirements.txt /app/# Install any needed packages specified in requirement.txtRUN pip install --trusted-host mirrors.aliyun.com -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone# Make port 6000 available to the world outside this containerEXPOSE 6000# Define environment variableENV envname=k8s# ADD application.py to /appADD application.py /app/CMD ["python", "application.py"]

创建镜像 docker build -t flaskk8s .创建 pod,创建 flask-deployment.yaml

apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: flasktest  name: flasktestspec:  selector:    matchLabels:      app: flasktest  replicas: 1  template:    metadata:      labels:        app: flasktest    spec:      containers:      - image: flaskk8s        name: flaskweb        imagePullPolicy: Never        ports:        - containerPort: 6000

因为是本地的镜像,所以在加上imagePullPolicy: Never ,否则 k8s 默认是会从 dockerhub 上去拉取。

# kubectl get podNAME                            READY   STATUS    RESTARTS   AGEflasktest-68cfdcc66d-d2tb7      1/1     Running   0          7sredis-master-7f88b489b9-k4c58   1/1     Running   0          126m# kubectl get deploymentNAME           READY   UP-TO-DATE   AVAILABLE   AGEflasktest      1/1     1            1           44sredis-master   1/1     1            1           127m

创建 flasktest 的 service,让其可以通过浏览器访问

apiVersion: v1kind: Servicemetadata:  name: flask-service  labels:    name: flaskservicespec:  type: NodePort  ports:  - port: 6000    nodePort: 30002  selector:    app: flasktest
# kubectl get serviceNAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGEflask-service     NodePort    10.97.54.167    <none>        6000:30002/TCP   15skubernetes        ClusterIP   10.96.0.1       <none>        443/TCP          3d5hredis-master-sr   ClusterIP   10.99.187.220   <none>        6379/TCP         79m

可以看到 30002 端口已经被暴露出来,之后我们访问http://127.0.0.1:30002/ ,看到可以正常的访问

在这里插入图片描述

一切看着都很顺利对不对,但是我们来考虑两个问题

  1. 如果 redis 的 pod 挂掉会怎么样?
  2. 如果创建 redis 时replicas为大于 1 时,那么指定某个 POD 的的 IP 是否妥当?

第一个问题,由于是使用 deployment 创建的 rs,再创建的 pod,此时如果 redis 的某个 pod 挂了,由于 rs 中定义了replicas: 1,它会重新再起一个 redis 的 pod,此时的 IP 可能就会变了。我们来试验一下,只需要将原来的 pod 删除掉即可,k8s 会自动再创建一个新的 pod

# kubectl get podNAME                            READY   STATUS    RESTARTS   AGEflasktest-68cfdcc66d-d2tb7      1/1     Running   0          27mredis-master-7f88b489b9-k4c58   1/1     Running   0          154m# kubectl delete pod redis-master-7f88b489b9-k4c58pod "redis-master-7f88b489b9-k4c58" deleted# kubectl get podNAME                            READY   STATUS    RESTARTS   AGEflasktest-68cfdcc66d-d2tb7      1/1     Running   0          27mredis-master-7f88b489b9-6kk8l   1/1     Running   0          12s

我们先将redis-master-7f88b489b9-k4c58这个 pod 删除掉,之后 k8s 会自动又创建了新的 pod redis-master-7f88b489b9-6kk8l

此时再访问 http://127.0.0.1:30002/ 则报错

redis.exceptions.ConnectionError: Error 113 connecting to 10.1.0.59:6379. No route to host.

在这里插入图片描述

报 10.1.0.59:6379 连接失败了。

第二个问题,我们设置了replicas的数量是为了做负载均衡,所以如果你在应用里将 ip 写死的话那就起不到负载均衡了。

所以使用了 k8s,如果要访问其它 pod 的话,则不可以将对方的 ip 直接写死到应用中的,我们需要通过 服务 来将各个 pod 进行通信。

1.4 创建 redis 的 service

Service 就是为了能让应用有个稳定的入口,如这里的 redis 访问我们的应用服务,我们先将上面创建 redis 的 pod 通过 service 将端口暴露出来

apiVersion: v1kind: Servicemetadata:  name: redis-master-sr  labels:    name: redis-masterspec:  ports:  - port: 6379    targetPort: 6379  selector:    app: redis

通过 kubectl get service -o wide 查看 service 详情

kubectl get service -o wideNAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE    SELECTORkubernetes        ClusterIP   10.96.0.1       <none>        443/TCP    3d3h   <none>redis-master-sr   ClusterIP   10.99.187.220   <none>        6379/TCP   44s    name=redis-master

可以看到有一个 type 为 ClusterIP 的 service,这有一个 ip,10.99.187.220 使用了 6379 作为对外端口,我们是不能在 k8s 外部通过这个 IP 的 6379 端口访问到 redis-master-sr 这个 service。但是如果在 k8s 里的相同网络应用,是可以通过这个CLUSTER-IP 来访问到的。

我们来试一下

if os.environ.get("envname") == "k8s": # 说明是在 k8s 中    REDIS_URL = "redis://{}:{}/{}".format('10.99.187.220', 6379, 1)else:    REDIS_URL = "redis://{}:{}/{}".format('127.0.0.1', 6380, 1)

重新打镜像包 docker build -t flaskk8s:ClusterIP . 创建一个 flaskk8s,tag 为 ClusterIP,修改 flask-deployment.yaml

apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: flasktest  name: flasktestspec:  selector:    matchLabels:      app: flasktest  replicas: 1  template:    metadata:      labels:        app: flasktest    spec:      containers:      - image: flaskk8s:ClusterIP        name: flaskweb        imagePullPolicy: Never        ports:        - containerPort: 6000

执行 kubectl apply -f flask-deployment.yaml 生效,再重新访问 http://127.0.0.1:30002/ 则又可以正常访问了。

1.5 使用环境变量来访问 service

使用 service 的 ClusterIP 虽然可以解决了由于 pod 的重启更换 IP 的问题,但是如果一个 service 重启,或者环境重新部署了,那么 service 的 IP 又会变了,此时就要重新修改代码了,这肯定是不行的。

我们使用 exec 命令进入到 pod 内部,使用 env 命令查看系统的环境变量

# kubectl get podNAME                            READY   STATUS    RESTARTS   AGEflasktest-74865c4b59-6l86m      1/1     Running   0          19mflasktest-74865c4b59-k4pkk      1/1     Running   0          19mredis-master-7f88b489b9-6kk8l   1/1     Running   0          160m# kubectl exec -it flasktest-74865c4b59-6l86m /bin/bashroot@flasktest-74865c4b59-6l86m:/app# envKUBERNETES_SERVICE_PORT_HTTPS=443KUBERNETES_SERVICE_PORT=443REDIS_MASTER_SR_PORT_6379_TCP_PROTO=tcpHOSTNAME=flasktest-74865c4b59-6l86mPYTHON_VERSION=3.5.7envname=k8sREDIS_MASTER_SR_PORT_6379_TCP_PORT=6379PWD=/appREDIS_MASTER_SR_SERVICE_HOST=10.103.116.170REDIS_MASTER_SR_PORT=tcp://10.103.116.170:6379HOME=/rootLANG=C.UTF-8KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443REDIS_MASTER_SR_PORT_6379_TCP=tcp://10.103.116.170:6379GPG_KEY=97FC712E4C024BBEA48A61ED3A5CA953F73C700DTERM=xtermSHLVL=1REDIS_MASTER_SR_PORT_6379_TCP_ADDR=10.103.116.170KUBERNETES_PORT_443_TCP_PROTO=tcpPYTHON_PIP_VERSION=19.3.1KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1REDIS_MASTER_SR_SERVICE_PORT=6379PYTHON_GET_PIP_SHA256=b86f36cc4345ae87bfd4f10ef6b2dbfa7a872fbff70608a1e43944d283fd0eeeKUBERNETES_SERVICE_HOST=10.96.0.1KUBERNETES_PORT=tcp://10.96.0.1:443KUBERNETES_PORT_443_TCP_PORT=443PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/ffe826207a010164265d9cc807978e3604d18ca0/get-pip.pyPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin_=/usr/bin/env

看到有两个和 redis service 有关的环境变量

REDIS_MASTER_SR_SERVICE_HOST=10.103.116.170REDIS_MASTER_SR_SERVICE_PORT=6379

k8s 会为每个 pod 的容器里都增加一组 service 相关的环境变量,也会随着 pod 或者 service 的变化而变化,有了这两个环境变量,我们就可以动态获取 IP,修改代码

if os.environ.get("envname") == "k8s": # 说明是在 k8s 中    redis_server = os.environ.get("REDIS_MASTER_SR_SERVICE_HOST")    redis_port = os.environ.get("REDIS_MASTER_SR_SERVICE_PORT")    REDIS_URL = "redis://{}:{}/{}".format(redis_server, redis_port, 1)else:    REDIS_URL = "redis://{}:{}/{}".format('127.0.0.1', 6380, 1)

这时我们就可以不用修改 IP 来适应 pod 或者 service 的变动了。

二、不同网络下的不同 pod 间通信

我们使用环境变量来解析服务的 IP,但是使用环境变量有一个限制,所有的 pods 须在一个 namespace 中,也就是说在同一个 namespace 中的 pod 才会共享环境变量,如果不在同一个 namespace 该如何访问呢?我们还是以这个 flask 应用为例,这次我们将 redis 放到 default 的 namespace 中,flask 的应用放到 yyxtest 的 namespace 中。

2.1 创建 redis 的 pod 与 service

redis.yaml

apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: redis  name: redis-masterspec:  selector:    matchLabels:      app: redis  replicas: 1  template:    metadata:      labels:        app: redis    spec:      containers:      - image: redis        name: redis-master2        ports:        - containerPort: 6379

redis-service.yaml

apiVersion: v1kind: Servicemetadata:  name: redis-master-sr  labels:    name: redis-masterspec:  ports:  - port: 6379    targetPort: 6379  selector:    app: redis

查看 pod 与 service 信息

# kubectl get podsNAME                            READY   STATUS      RESTARTS   AGEredis-master-wjq6t              1/1     Running     0          8m55sC:\Users\54523\Desktop\k8stest>kubectl get serviceNAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGEredis-master-sr   ClusterIP   10.97.140.58     <none>        6379/TCP         3m3s

创建 python 应用

#-*- coding:utf-8 -*-# author:Yang# datetime:2020/2/10 16:07# software: PyCharmfrom flask import Flaskfrom flask_redis import FlaskRedisimport timeimport osif os.environ.get("envname") == "k8s": # 说明是在 k8s 中    redis_server = os.environ.get("REDIS_MASTER_SR_SERVICE_HOST")    redis_port = os.environ.get("REDIS_MASTER_SR_SERVICE_PORT")    REDIS_URL = "redis://{}:{}/{}".format(redis_server, redis_port, 1)else:    REDIS_URL = "redis://{}:{}/{}".format('127.0.0.1', 6380, 1)app = Flask(__name__)app.config['REDIS_URL'] = REDIS_URLredis_client = FlaskRedis(app)@app.route("/")def index_handle():    redis_client.set("reidstest",time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time())))    name = redis_client.get("reidstest").decode()    return "hello %s"% nameapp.run(host='0.0.0.0', port=6000, debug=True)

上面是之前的代码,采用环境变量的方式,获取到 redisserver 和 redisport,之前创建 flask 的应用的 pod 和 service 都没有指定 namespace,如果没有指定的话,默认是创建在了 default 的 namespace,由于 redis 也没有指定,所以它们之间是可以通过共享环境变量来解决服务地址的,那现在我们将 python 应用创建在 yyxtest 的 namespace 中,看看情况如何。

将先之前 python 应用打包 docker build -t flaskk8s:dns .

创建 deployment.yaml

apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: flasktest  name: flasktest  namespace: yyxtestspec:  selector:    matchLabels:      app: flasktest  replicas: 2  template:    metadata:      labels:        app: flasktest    spec:      containers:      - image: flaskk8s:dns        name: flaskweb        imagePullPolicy: Never        ports:        - containerPort: 6000

再创建 service.yaml

apiVersion: v1kind: Servicemetadata:  name: flask-service  labels:    name: flaskservice  namespace: yyxtestspec:  type: NodePort  ports:  - port: 6000    nodePort: 30003  selector:    app: flasktest

这次在 deployment 和 service 中的 metadata 中都添加了namespace: yyxtest ,它们将会被创建到 yyxtest 的 namespace 中

使用kubectl get pods --namespace=yyxtest 来查看 pods,此时 pods 直接显示 error 了,通过查看 pods 里的日志发现,redis_port = os.environ.get("REDIS_MASTER_SR_SERVICE_PORT") 这行代码没有获取到REDIS_MASTER_SR_SERVICE_PORT的值,返回的是个 None,所以在之后的 redis 初始化时就报错失败了,这也说明,在 yyxtesxt 的名称空间中的 pod 里是没有REDIS_MASTER_SR_SERVICE_PORT 这个环境变量的。

我们同样在 default 的 namespace 中也创建 flask 的 pod 和 service,此时就可以正常的访问。

我们使用 kubectl exec 命令分别进入到两个 namespace 空间中的 flask 应用的 pod 中

在 default 的名称空间中

# env...FLASK_SERVICE_SERVICE_HOST=10.109.55.91REDIS_MASTER_SR_SERVICE_PORT=6379...

它包含有这两个环境变量,但是在 yyxtest 的 pod 中却没有这两个环境变量,这也就说明,原来的代码在非 default 空间(准确的说是和 redis 不在同一个空间中)是不能正常运行的。

使用 dns 来解析服务地址

除了可以使用环境变量来解析服务地址,用的更多的应该是使用 dns 来解析了,在创建 redis 的 service 时,Kubernetes 会创建一个相应的 DNS 条目,该条目的形式是 <service-name>.<namespace-name>.svc.cluster.local,这意味着如果容器只使用 <service-name>,它将被解析到本地命名空间的服务。比如在 yaml 文件中设置了

metadata:  name: redis-master-sr

则会创建一个 redis-master-sr.default.svc.cluster.local的记录, 我们在 default 名称空间中的 pod 试一下

# ping redis-master-srPING redis-master-sr.default.svc.cluster.local (10.97.140.58) 56(84) bytes of data.

在 yyxtest 名称空间中的 pod 再试一下

# ping redis-master-srping: redis-master-sr: Name or service not known

说明服务名只能在它所在的空间中(本例中的 default)有 dns 记录,不在它的空间(本例中的 yyxtest)中则不存在,但是我们注意到,在 default 空间中 redis-master-sr 解析到了redis-master-sr.default.svc.cluster.local ,那么在非 default 空间中是否可以正常解析redis-master-sr.default.svc.cluster.local 这个名称呢?

在 yyxtest 的 pod 中执行

ping redis-master-sr.default.svc.cluster.localPING redis-master-sr.default.svc.cluster.local (10.97.140.58) 56(84) bytes of data.

也是可以正常解析的,所以这时我们来修改一下 python 的代码,port 我们使用了一个固定的值,6379,因为 port 不会随着 pod 或者 service 的重启而发生变化,只要在 yaml 文件中定义好了就不会改变了

if os.environ.get("envname") == "k8s": # 说明是在 k8s 中    REDIS_URL = "redis://{}:{}/{}".format("redis-master-sr.default.svc.cluster.local", 6379, 1)else:    REDIS_URL = "redis://{}:{}/{}".format('127.0.0.1', 6380, 1)

现在应用又可以正常的工作了,我们就可以和 redis 不同的名称空间创建应用了。

参考文章

命名空间

阅读全文: http://gitbook.cn/gitchat/activity/5e43ac77bd4a894bc95d163a

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

发布了3634 篇原创文章 · 获赞 3487 · 访问量 325万+

猜你喜欢

转载自blog.csdn.net/valada/article/details/104289026