Python Flask demo配合consul实现配置分离和服务注册功能

背景

最近笔者在对一个业务集群进行微服务的改造,其中就涉及到配置分离的场景。经过和开发组同事共同论证后,最终选择了当前很流行的kubernetes + consul架构。今天先来介绍一个demo,大致介绍consul配置中心的实现原理,也可以把这个当成是本地开发环境调试的参考。

demo架构以及设计思路

架构图如下:

python-consul-redis-demo-diagram

这个demo是以一个开发者在自己的电脑搭建一套本地开发环境为背景去设计的,所以架构图里面涉及到的各个模块都是直接运行在本地。Flask应用启动后,从consul读取redis-server的连接配置,随后连接到redis-server。在接收到http请求后,读取redis-server中的数据,并展示在web页面上。

剧透最终运行效果:

demo

demo部署

运行redis-server

在当前版本的redis已经没有提供windows系统下的exe文件了,所以我们使用win10的WSL去运行redis-server。

微软官方文档: Install WSL

下载redis

1
wget https://download.redis.io/releases/redis-6.2.6.tar.gz

编译安装

1
2
3
tar -zxf redis-6.2.6.tar.gz
cd redis-6.2.6
make

运行

1
2
3
4
5
6
# 在默认配置中添加一个密码设置
cd redis-6.2.6
echo "requirepass aaaa1111" >> redis.conf

# 运行redis
src/redis-server redis.conf

测试redis

1
2
3
4
5
6
7
rondo@wsl:~/redis-6.2.6/src$ ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> auth aaaa1111
OK
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> get foo
"bar"

运行consul服务

consul development mode

consul可以以开发模式(development mode)运行,在开发模式下,尽管会有安全性和稳定性的隐患,但可以让我们很方便地在本地开发环境测试consul功能。

consul的开发模式非常适合用在我们的本地开发环境中,至于到后面到了线上测试环境乃至生产环境,自然就会有完整的consul集群了,我们会在后面再探讨这个问题。

下载consul

在consul官网的下载页面选择合适的操作系统版本进行下载即可,在本例中,我们下载windows 64 bit版本的consul。

安装consul

consul不需要安装,下载后解压就可以直接使用。在本例中,把consul.exe存放在路径c:\consul中。

启动consul

在windows的cmd.exe窗口中,在运行参数中指定-dev,consul就会以开发模式启动,就不需要其他额外的设置了。 再加上-ui参数,consul正常启动后,我们可以通过http://127.0.0.1:8500/ui 进入consul的web界面进行进一步的配置。

1
C:\consul>consul.exe agent -dev -ui

consul-development-mode

在consul中添加应用配置(Key/Value)

在后面展示的python应用中,我们需要定义redis的连接配置,以及一条展示信息。我们使用的配置内容如下:

1
2
3
4
5
6
7
8
9
{
"redis_config": {
"redis_host": "127.0.0.1",
"redis_port": 6379,
"redis_db": 0,
"redis_password": "aaaa1111"
},
"show_message": "this is a python-consul-redis demo"
}

在consul的web UI中,我们可以很方便地使用Key/Value功能创建一组配置内容demo-config

consul-key-value

python/flask应用

python读取consul中的Key/Value内容

Consul官方有提供python客户端,只需要简单的两行代码就可以获取到consul中的Key Value内容:

1
2
3
4
5
6
7
8
C:\Users\rondochen>python
Python 3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 19:00:18) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import consul
>>> c = consul.Consul(host='127.0.0.1', port=8500, scheme='http')
>>> c.kv.get('demo-config')
('86', {'LockIndex': 0, 'Key': 'demo-config', 'Flags': 0, 'Value': b'{\n "redis_config": {\n\t\t"redis_host": "127.0.0.1",\n\t\t"redis_port": 6379,\n\t\t"redis_db": 0,\n\t\t"redis_password": "aaaa1111"\n\t},\n\t"show_message": "this is a python-consul-redis demo"\n}', 'CreateIndex': 72, 'ModifyIndex': 86})
>>>

我们从上面的输出可以看到,我们需要的key value内容存放在一个元组中的一个字典中,并是以byte方式记录的,还需要转成json格式:

1
2
3
4
5
6
import consul
import json

try:
c = consul.Consul(host='127.0.0.1', port=8500, scheme = 'http' )
config_json = json.loads(c.kv.get('demo-config')[1]['Value'].decode('utf-8'))

使用consul进行服务注册(顺带一提)

服务注册一般会和服务发现放在一起介绍,简单来说,我们之所以要使用微服务架构,就是为了让多个针对不同场景的服务可以尽可能地解耦,以适应便捷的开发需求。为了让各个微服务能方便地请求到自己的上游数据,所有的微服务都要到consul上去注册自己的信息。在本文中,我们可以使用几行简单的代码实现服务注册需求:

1
2
3
4
5
import consul

c = consul.Consul(host='127.0.0.1', port=8500, scheme = 'http' )
check_demo = consul.Check.http('http://127.0.0.1:5000',interval='2s')
c.agent.service.register('mydemo', address='127.0.0.1', port=5000,check=check_demo)

当这个叫做mydemo的服务注册到consul中后,我们可以在consul的web UI中查看到如下结果:

consul-service-register

我们还可以使用consul DNS查看这个服务的地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
rondo@win10:~$ dig @127.0.0.1 -p 8600 mydemo.service.consul

; <<>> DiG 9.16.1-Ubuntu <<>> @127.0.0.1 -p 8600 mydemo.service.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7576
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;mydemo.service.consul. IN A

;; ANSWER SECTION:
mydemo.service.consul. 0 IN A 127.0.0.1

;; Query time: 1 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Sun Nov 21 13:06:38 CST 2021
;; MSG SIZE rcvd: 66

关于这个服务注册以及服务发现,主要会在后面做微服务容器化编排的时候使用上,在这里只是顺带介绍一下有这个场景。在本文的demo中,暂时不会引入这部分的功能。

flask应用读取redis内容

解决了配置文件获取的功能后,我们就可以直接进行应用的功能开发了。我们会初始化一个简单的flask应用,读写redis中的一个key,记录页面的访问次数。

整个demo的代码如下:

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
import time
import redis
import consul
import json
from flask import Flask

try:
c = consul.Consul(host='127.0.0.1', port=8500, scheme = 'http' )
config_json = json.loads(c.kv.get('demo-config')[1]['Value'].decode('utf-8'))

except:
exit()

redis_config = config_json['redis_config']
show_message = config_json['show_message']

app = Flask(__name__)
cache = redis.Redis(host=redis_config['redis_host'], port=redis_config['redis_port'], db=redis_config['redis_db'], password=redis_config['redis_password'])

def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)

@app.route('/')
def hello():
count = get_hit_count()
return '{}! I have been seen {} times.\n'.format(show_message,count)

if __name__ == "__main__":
app.run()

运行结果

demo

总结

最初笔者和开发团队讨论微服务改造的时候,因为考虑到需要对大量的应用服务进行配置管理,首先就进行了配置中心的讨论。在我们敲定技术栈之前,前期的验证工作就是类似本文这样的,打磨出一个最小的demo,验证技术可行性,同时也可以通过这个demo大致评估改造的工作量。

诚然,正如我们和开发组会议时候讨论到的,比起读取本地配置文件这种传统方法,使用consul配置中心在开发阶段会增加一点繁琐的操作。但在运维的角度看,当我们维护一个分布式的微服务集群时,consul提供的服务发现功能可以大大提高系统的健壮性,在配置中心的加持下,运维人员可以很轻松地维护一个统一的配置模板。

后面我们会使用这个demo,编排成一个Kubernetes中的服务,到时候配置中心的便捷性就能体现出来了。

参考文档

Python client for Consul.io

Run the Consul Agent

demo 代码中的flask部分参考自docker的官方文档:https://docs.docker.com/compose/gettingstarted/