6

kubernetes中pod间的通信

 3 years ago
source link: https://www.yangyanxing.com/article/pod-communite-k8s.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

  • 同一网络下的不同pod间是怎么通信的?
  • 同一个pod中不同的容器是怎么通信的?
  • 不同的网络下不同的pod是怎么通信的?

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

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

podcom1.png

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

1.1 创建redis pod

我们先创建一个redis pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: redis
name: redis-master
spec:
selector:
matchLabels:
app: redis
replicas: 1
template:
metadata:
labels:
app: redis
spec:
containers:
- image: redis
name: redis-master2
ports:
- containerPort: 6379

查看pod详细信息

1
2
3
4
5
# kubectl create -f r-deployment.yaml
deployment.apps/redis-master created
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-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作为对外端口

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

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

redis1.png

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#-*- coding:utf-8 -*-
# author:Yang
# datetime:2020/2/10 16:07
# software: PyCharm

from flask import Flask
from flask_redis import FlaskRedis
import time

REDIS_URL = "redis://{}:{}/{}".format('127.0.0.1', 6380, 1)


app = Flask(__name__)
app.config['REDIS_URL'] = REDIS_URL
redis_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"% name

app.run(host='0.0.0.0', port=6000, debug=True)

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

image-20200210171910360image-20200210171910360

1.3 在pod中访问redis

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

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

1
2
3
4
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呢?

1
2
3
4
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Use an official Python runtime as a parent image
From python:3.5.7
# Set the working directory to /app
WORKDIR /app
# ADD requirements.txt
COPY requirements.txt /app/
# Install any needed packages specified in requirement.txt
RUN 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 container
EXPOSE 6000
# Define environment variable
ENV envname=k8s
# ADD application.py to /app
ADD application.py /app/
CMD ["python", "application.py"]

创建镜像docker build -t flaskk8s .

创建pod,创建flask-deployment.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: flasktest
name: flasktest
spec:
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上去拉取。

1
2
3
4
5
6
7
8
# kubectl get pod
NAME READY STATUS RESTARTS AGE
flasktest-68cfdcc66d-d2tb7 1/1 Running 0 7s
redis-master-7f88b489b9-k4c58 1/1 Running 0 126m
# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
flasktest 1/1 1 1 44s
redis-master 1/1 1 1 127m

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

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: flask-service
labels:
name: flaskservice
spec:
type: NodePort
ports:
- port: 6000
nodePort: 30002
selector:
app: flasktest
1
2
3
4
5
# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
flask-service NodePort 10.97.54.167 <none> 6000:30002/TCP 15s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d5h
redis-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

1
2
3
4
5
6
7
8
9
10
11
12
# kubectl get pod
NAME READY STATUS RESTARTS AGE
flasktest-68cfdcc66d-d2tb7 1/1 Running 0 27m
redis-master-7f88b489b9-k4c58 1/1 Running 0 154m

# kubectl delete pod redis-master-7f88b489b9-k4c58
pod "redis-master-7f88b489b9-k4c58" deleted

# kubectl get pod
NAME READY STATUS RESTARTS AGE
flasktest-68cfdcc66d-d2tb7 1/1 Running 0 27m
redis-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.

flask3.png

报 10.1.0.59:6379 连接失败了。

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

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

1.4 创建redis的service

Service 就是为了能让应用有个稳定的入口,如这里的redis访问我们的应用服务,我们想

先将上面创建redis的pod通过service将端口暴露出来

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: redis-master-sr
labels:
name: redis-master
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis

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

1
2
3
4
kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes 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作为对外端口,我们是不能通过这个IP的6379端口访问到redis-master-sr这个service。但是如果在k8s里的相同网络应用,是可以通过这个CLUSTER-IP 来访问到的。

我们来试一下

1
2
3
4
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: flasktest
name: flasktest
spec:
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命令查看系统的环境变量

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
# kubectl get pod
NAME READY STATUS RESTARTS AGE
flasktest-74865c4b59-6l86m 1/1 Running 0 19m
flasktest-74865c4b59-k4pkk 1/1 Running 0 19m
redis-master-7f88b489b9-6kk8l 1/1 Running 0 160m

# kubectl exec -it flasktest-74865c4b59-6l86m /bin/bash
root@flasktest-74865c4b59-6l86m:/app# env
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
REDIS_MASTER_SR_PORT_6379_TCP_PROTO=tcp
HOSTNAME=flasktest-74865c4b59-6l86m
PYTHON_VERSION=3.5.7
envname=k8s
REDIS_MASTER_SR_PORT_6379_TCP_PORT=6379
PWD=/app
REDIS_MASTER_SR_SERVICE_HOST=10.103.116.170
REDIS_MASTER_SR_PORT=tcp://10.103.116.170:6379
HOME=/root
LANG=C.UTF-8
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
REDIS_MASTER_SR_PORT_6379_TCP=tcp://10.103.116.170:6379
GPG_KEY=97FC712E4C024BBEA48A61ED3A5CA953F73C700D
TERM=xterm
SHLVL=1
REDIS_MASTER_SR_PORT_6379_TCP_ADDR=10.103.116.170
KUBERNETES_PORT_443_TCP_PROTO=tcp
PYTHON_PIP_VERSION=19.3.1
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
REDIS_MASTER_SR_SERVICE_PORT=6379
PYTHON_GET_PIP_SHA256=b86f36cc4345ae87bfd4f10ef6b2dbfa7a872fbff70608a1e43944d283fd0eee
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/ffe826207a010164265d9cc807978e3604d18ca0/get-pip.py
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/env

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

1
2
REDIS_MASTER_SR_SERVICE_HOST=10.103.116.170
REDIS_MASTER_SR_SERVICE_PORT=6379

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

1
2
3
4
5
6
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的变动了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK