跳转至

k8s

kubernetes中使用dns来访问服务

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

创建redis的pod与service

redis.yaml

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

redis-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: redis-master-sr
  labels:
    name: redis-master
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    app: redis

查看pod与service信息

1
2
3
4
5
6
7
# kubectl get pods
NAME                            READY   STATUS      RESTARTS   AGE
redis-master-wjq6t              1/1     Running     0          8m55s

C:\Users\54523\Desktop\k8stest>kubectl get service
NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
redis-master-sr   ClusterIP   10.97.140.58     <none>        6379/TCP         3m3s

创建python应用

#-*- coding:utf-8 -*-
# author:Yang
# datetime:2020/2/10 16:07
# software: PyCharm

from flask import Flask
from flask_redis import FlaskRedis
import time
import os

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)


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)

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

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

创建deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: flasktest
  name: flasktest
  namespace: yyxtest
spec:
  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: v1
kind: Service
metadata:
  name: flask-service
  labels:
    name: flaskservice
  namespace: yyxtest
spec:
  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的名称空间中

1
2
3
4
5
# env
...
FLASK_SERVICE_SERVICE_HOST=10.109.55.91
REDIS_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-sr
PING redis-master-sr.default.svc.cluster.local (10.97.140.58) 56(84) bytes of data.

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

# ping redis-master-sr
ping: 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.local
PING redis-master-sr.default.svc.cluster.local (10.97.140.58) 56(84) bytes of data.

也是可以正常解析的,所以这时我们来修改一下python的代码

1
2
3
4
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不同的名称空间创建应用了。

参考文章

命名空间

kubernetes中pod间的通信

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

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

kubernetes中的服务类型

当我们使用deployment或者RS创建了一些pod时,比如创建了一个nginx的pod,该pod中有三个replicas,此时,如果我们查看pod状态大概是这个样子的

使用kubernetes搭建简单的应用

最近在看kubernetes的相关内空,参考《Kubernetes权威指南》第一章,搭建一个简单的应用,它里面使用的是RC,我直接使用RS来搭建。

项目架构

我们通过kubernetes来部署一个应用,这个应用后台是一个php网站,数据库使用redis,redis采用一主两从的部署方式,做到读写分离,写操作走redis主库,读操作走reids从库。并且启动多个应用副本,达到负载均衡,总体的应用架构如下图

创建redis-master 服务

老版本的kubernetes使用RC(ReplicationController)来创建pod,但是随着RS(ReplicationSet)的出现,已经取代了RC,所以我们直接采用RS来创建pod,创建redis-master.yaml文件

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  replicas: 1
  selector:
    matchLabels:
      name: redis-master
  template:
    metadata:
      labels:
        name: redis-master
    spec:
      containers:
      - name: r-master
        image: kubeguide/redis-master
        ports: 
        - containerPort: 6379

yaml文件的编写很是麻烦,尤其是apiVersion: apps/v1 的选择,不同的apiVersion 对应下面的指令不同,写好的yaml文件可以在 https://kubeyaml.com/ 这个网站上校验一下。

通过 kubectl create -f redis-master.yaml 命令创建ReplicaSet, 使用 kubectl get pod 来查看pod的创建情况

1
2
3
# kubectl get pod
NAME                 READY   STATUS              RESTARTS   AGE
redis-master-n6wbp   0/1     ContainerCreating   0          11s

过一会就可以创建成功了,过程中通过status查看状态,有时会创建失败,失败的原因很多,遇到到在网上搜索一下吧。到时候再进行总结。

创建完pod以后,如果没有创建与之对应的service,则该pod也无法正常工作,接下来先创建这个service

apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    name: redis-master

使用 kubectl create -f redis-master-service.yaml 创建service,使用kubectl get services 查看service状态。

1
2
3
4
5
6
# kubectl create -f redis-master-service.yaml 
service/redis-master created
# kubectl get services
NAME           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP    10d
redis-master   ClusterIP   10.109.162.197   <none>        6739/TCP   10s

看到redis-master使用了10.109.162.197 这个虚拟的ip,之后创建的pod可以使用这个ip和端口访问这个redis服务。

通过kubernetes创建的pod,虚拟IP是动态的,如果重启以后可能就会变了,所以其它的服务如redis-slave要同步的话是不能使用这个虚拟IP的,kubernetes通过环境变量来记录从服务到虚拟IP的关系。

创建redis-slave 服务

创建redis-slave.yaml文件

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: redis-slave
  labels:
    name: redis-slave
spec:
  replicas: 2 #创建两个副本
  selector:
    matchLabels:
      name: redis-slave
  template:
    metadata:
      labels:
        name: redis-slave
    spec:
      containers:
      - name: r-slave
        image: kubeguide/guestbook-redis-slave
        env: #添加环境变量
        - name: GET_HOSTS_FROM 
          value: env
        ports: 
        - containerPort: 6379
1
2
3
4
5
6
7
# kubectl create -f redis-slave.yaml 
replicaset.apps/redis-slave created
# kubectl get pod
NAME                 READY   STATUS              RESTARTS   AGE
redis-master-n6wbp   1/1     Running             0          37m
redis-slave-7zj5x    0/1     ContainerCreating   0          12s
redis-slave-hp2pt    0/1     ContainerCreating   0          12s

我们来查看一下redis-slave中的环境变量

# kubectl get pod
NAME                 READY   STATUS    RESTARTS   AGE
redis-master-n6wbp   1/1     Running   0          39m
redis-slave-7zj5x    1/1     Running   0          2m7s
redis-slave-hp2pt    1/1     Running   0          2m7s
root@kubernetes-master:/usr/local/docker/kubernetes/yaml/phpredis# kubectl exec redis-slave-7zj5x env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=redis-slave-7zj5x
GET_HOSTS_FROM=env
REDIS_MASTER_SERVICE_HOST=10.109.162.197
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT_6739_TCP=tcp://10.109.162.197:6739
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
REDIS_MASTER_PORT_6739_TCP_PORT=6739
REDIS_MASTER_PORT_6739_TCP_ADDR=10.109.162.197
KUBERNETES_SERVICE_PORT=443
REDIS_MASTER_PORT_6739_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
REDIS_MASTER_PORT=tcp://10.109.162.197:6739
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
REDIS_VERSION=3.0.3
REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.0.3.tar.gz
REDIS_DOWNLOAD_SHA1=0e2d7707327986ae652df717059354b358b83358
HOME=/root

可以看到

REDIS_MASTER_SERVICE_HOST=10.109.162.197
REDIS_MASTER_SERVICE_PORT=6379

可以通过这两个环境变量来动态获取上面redis-master的IP与PORT

然后再创建与redis-slave对应的service服务

apiVersion: v1
kind: Service
metadata:
  name: redis-slave
  labels:
    name: redis-slave
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    name: redis-slave

通过kubectl get service 看到现在已经有三个服务了。

1
2
3
4
5
# kubectl get service
NAME           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP    10d
redis-master   ClusterIP   10.109.162.197   <none>        6379/TCP   31m
redis-slave    ClusterIP   10.104.211.17    <none>        6379/TCP   18s

创建PHP应用

yaml文件如下

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: phpserver
  labels:
    name: phpserver
spec:
  replicas: 3
  selector:
    matchLabels:
      name: phpserver
  template:
    metadata:
      labels:
        name: phpserver
    spec:
      containers:
      - name: phpfront
        image: kubeguide/guestbook-php-frontend
        env:
        - name: GET_HOSTS_FROM
          value: env
        ports: 
        - containerPort: 80

创建pod,并查看pod状态

# kubectl create -f phpfront.yaml 
replicaset.apps/phpserver created
# kubectl get pods
NAME                 READY   STATUS              RESTARTS   AGE
phpserver-5bwzh      0/1     ContainerCreating   0          7s
phpserver-6v4dd      0/1     ContainerCreating   0          7s
phpserver-dbdmn      0/1     ContainerCreating   0          7s
redis-master-n6wbp   1/1     Running             0          53m
redis-slave-7zj5x    1/1     Running             0          16m
redis-slave-hp2pt    1/1     Running             0          16m

创建相应的service

apiVersion: v1
kind: Service
metadata:
  name: phpservice
  labels:
    name: phpservice
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30001
  selector:
    name: phpserver

运行创建命令并且查看services

1
2
3
4
5
6
7
8
# kubectl create -f phpservice.yaml 
service/phpservice created
# kubectl get services
NAME           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP        10d
phpservice     NodePort    10.100.94.114    <none>        80:30001/TCP   9s
redis-master   ClusterIP   10.109.162.197   <none>        6739/TCP       45m
redis-slave    ClusterIP   10.104.211.17    <none>        6739/TCP       14m

注意到phpservice的type为NodePort,ports为80:30001/TCP,则这时可以通过主机的30001端口来访问该应用

访问 http://192.168.206.128:30001/ 来访问应用

应用更新

应用回滚

动态调整资源

可以手工的调整pod资源数量,如网站今天要做活动,预料到流量会增加,需要增加一些副本,则可以使用

kubectl scale rs rsname --replicas=4 命令来改变副本的数量,--replicas 值为要修改的量,当活动结束以后再手工的改回来。

当然对于这种纯手工的来修改副本数,还是不够智能,我们真正希望的是,当一个网站流量变大的时候可以自动的扩容,当网站的流量减少的时候,可以自动的缩容。

这个可以通过 HPA 来实现

一些问题

  1. 环境变量是怎么传递的

  2. spec.type的类型

Kubernetes的三种外部访问方式