跳转至

Blog

使用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的三种外部访问方式

flask中跳转的同时设置cookie

最近在使用flask的时候,有一个比较麻烦的事情,在跳转网页的时候需要设置cookie,使用单独设置cookie与单独跳转都比较简单,

跳转网页

1
2
3
4
5
from flask import  redirect

@app.route("/redirect")
def redirecttest():
    return redirect("/test")
from flask import Flask,make_response

app = Flask(__name__)

@app.route("/set_cookie")
def set_cookie():
    #先创建响应对象
    resp = make_response("set cookie test")
    # 设置cookie  cookie名 cookie值 默认临时cookie浏览器关闭即失效
    # 通过max_age控制cookie有效期, 单位:秒
    resp.set_cookie("display","yangyanxing",max_age=3600) 
    return resp

可以看到,设置cookie的response 最后是通过return 回来的,但是上面的网页跳转则是通过redirect() 函数返回的,所以这两个没法同时的使用。

查看redirect() 的源码

def redirect(location, code=302, Response=None):    
    if Response is None:
        from .wrappers import Response

    display_location = escape(location)
    if isinstance(location, text_type):
        # Safe conversion is necessary here as we might redirect
        # to a broken URI scheme (for instance itms-services).
        from .urls import iri_to_uri

        location = iri_to_uri(location, safe_conversion=True)
    response = Response(
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
        "<title>Redirecting...</title>\n"
        "<h1>Redirecting...</h1>\n"
        "<p>You should be redirected automatically to target URL: "
        '<a href="%s">%s</a>.  If not click the link.'
        % (escape(location), display_location),
        code,
        mimetype="text/html",
    )
    response.headers["Location"] = location
    return response

可以看到,其实redirect函数也是通过构造一个response最后再将这个resonse return 出去,所以我们是否可以构造一个类似于redirect函数中的response对象,然后设置好status code是不是就可以了呢?

from flask import Flask,make_response,escape
app = Flask(__name__)

@app.route("/test")
def test():
    response = make_response(
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
        "<title>Redirecting...</title>\n"
        "<h1>Redirecting...</h1>\n"
        "<p>You should be redirected automatically to target URL: "
        '<a href="%s">%s</a>.  If not click the link.'
        % (escape("https://www.baidu.com"),
           escape("https://www.baidu.com")), 302)
    response.set_cookie('display',"yangyanxing")
    return respose

运行上面的代码,发现停留在/test 页面,显示上面的make_response的文字,并没有跳转,但是cookie中已经有display的cookie了,说明设置cookie成功了,跳转也成功了一半,只是它没有真正的跳转,还需要在网页上点击一下。

redirect函数中是通过 response.headers["Location"] = location 来设置的,在make_response 的返回响应对象中是通过response.location = 'xxxx' 来设置的

from flask import Flask,make_response,escape
app = Flask(__name__)

@app.route("/test")
def test():
    response = make_response(
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
        "<title>Redirecting...</title>\n"
        "<h1>Redirecting...</h1>\n"
        "<p>You should be redirected automatically to target URL: "
        '<a href="%s">%s</a>.  If not click the link.'
        % (escape("https://www.baidu.com"),
           escape("https://www.baidu.com")), 302)
    response.set_cookie('display',"yangyanxing")
    `response.location = escape("https://www.baidu.com")
    return respose

这时就可以正常的跳转了

pymongo中的并发问题

在写一个系统时不得不考虑高并发时数据库的写入准确性问题,拿mongodb来说,比如要插入一条数据,如果存在则更新,如果不存在则插入新数据,如果在多线程会有哪些问题呢?

python中自定义loggingHandler

最近在python中使用日志处理的时候有一个新的需求,将日志写入到数据库中,一开始比较懒,写一个循环,定时的去读本地日志文件,将里面的内容直接写到数据库中,很显然这种处理很low,后来又在外面写了一个服务,但本质上还是用循环读的方式将读出来的内容发个post请求然后入库,这两种方式本质上是一样的,而且第二种在发post请求的时候还遇到了内容过多导致服务解析失败的问题。

神经网络中常见激活函数的python表达

在神经网络最后的输出层,通常会使用激活函数将最后一层神经元得到的数据再进行计算,最终得到一个输出的结果,这里称该函数为激活函数,比对输出表达式为,当最后一层输出元得到的数据大于0,则输出1,小于0则输出0,这种称为阶跃函数,常见的激活函数有阶跃函数,sigmoid,ReLU,tanh函数

numpy中对于矩阵的基本操作

最近着手于机器学习,但是苦于已将数学知识还给了老师多年,看着满屏的数学公式尤为头疼。 还好看了一篇关于卷积神经网络的数学基础以后,发现慢慢的可以看懂一些东西了,这里也记录一下自己的所学,千里之行始于足下,先从最基础的开学学习。

python中使用虚拟环境

在进行python的开发过程中一直倡导使用虚拟环境来进行项目隔离,这样不会因为python的包不同而导致各种问题,但是以往为了图省事简单,安装包就一直使用pip install 进行全局安装,这样做其实很不好,最近也开始尝试使用虚拟环境来对项目进行隔离开发。

字符串

  1. 字符串只能用双引号定义,
  2. go里单引号和双引号不通过,可以使用`` 号来定义原始输出,不会进行转义,相当于python 里的三引号

go语言中关于文件路径的使用总结

在编程中操作文件非常常见,如读取或者写入文件,但是在此之前会先构造出文件路径,在python中有相应的os.path库,对于文件路径的操作很简单,如 - os.path.basename 获取文件名 - os.path.dirname 获取文件目录名 - os.path.join 拼接文件路径 - os.path.splitext 拆分文件,获取文件名和扩展名 - os.path.adspath 获取文件绝对路径 - os.path.exists 判断文件或者文件夹是否存在

以下尝试在go语言中对上面的这些方法进行实现