此章节为《Docker开发指南》的第六章—— 创建一个简单的web应用 的个人读书笔记
本章节内容如下:
- 创建一个基本网页
- 利用现有镜像
- 实现缓存功能
- 微服务
- 总结
想要创建一个简单的identicon独一无二的图像的Web应用程序
案例可以从书的github获取代码
git clone -b v0 https://github.com/using-docker/creating-a-simple-web-app/
1. 创建一个基本网页
为了简单起见,用字符串返回html,把identidock.py换成以下内容:
from flask import Flask
app = Flask(__name__)
default_name = 'Joe Bloggs'
@app.route('/')
def mainpage():
name = default_name
header = '<html><head><title>Identidock</title></head><body>'
body = '''<form method="POST">
Hello <input type="text" name="name" value="{}">
<input type="submit" value="submit">
</form>
<p>You look like a:
<img src="/monster/monster.png"/>
'''.format(name)
footer = '</body></html>'
return header + body + footer
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
format的{}替换成了name变量的值,然后执行
docker-compose up -d
打开浏览器访问http://localhost:5000,得到简单的网页
2. 利用现有镜像
这个实例中,我们会有用到一个现有的Docker镜像dnmonster,它可以将字符串变成独一无二的图像,还提供了差不多的RESTful API供我们使用,我们还可以将另一个的identicon服务将dnmonster替换掉。我们做如下修改
from flask import Flask, Response, request
import requests
app = Flask(__name__)
default_name = 'Joe Bloggs'
@app.route('/')
def mainpage():
name = default_name
header = '<html><head><title>Identidock</title></head><body>'
body = '''<form method="POST">
Hello <input type="text" name="name" value="{}">
<input type="submit" value="submit">
</form>
<p>You look like a:
<img src="/monster/monster.png"/>
'''.format(name)
footer = '</body></html>'
return header + body + footer
@app.route('/monster/<name>')
def get_identicon(name):
r = requests.get('http://dnmonster:8080/monster/' + name + '?size=80')
image = r.content
return Response(image, mimetype='image/png')
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
- 导入Flask的Response返回图像
- 导入Requests库,用于与dnmonster服务通讯
- 发送一个http get请求到dnmonster,对应name变量值的identicon图像,大小为80像素
- return一个png图片
接下来对Dockerfile做一个小修改,使得代码能够使用正确的程序库
FROM python:3.4
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install Flask==0.10.1 uWSGI==2.0.8 requests==2.5.1
WORKDIR /app
COPY app /app
COPY cmd.sh /
EXPOSE 9090 9191
USER uwsgi
CMD ["/cmd.sh"]
准备就绪,从docker hub拉下dnmonster镜像
docker build -t indenidock .
docker run -d --name dnmonster amouat/dnmonster:1.0
或者指定在8080访问dnmonster
docker run -d -p 5000:5000 -e "ENV=DEV" --link dnmonster:dnmonster identidock
当你再次打开htttp://localhost:5000,可以获得一个输入字符串,得到identicon的画面
但是。。。。。提交按钮还没有生效,我们将compose重拾起来,还要记住docker run,按照下列内容更新docker-compose.yml:
identidock:
build: .
ports:
- "5000:5000"
environment:
ENV: DEV
volumes:
- ./app:/app
links:
- dnmonster
- redis
dnmonster:
image: amouat/dnmonster:1.0
redis:
image: redis:3.0
- 声明从identidock到dnmonster容器的连接,Compose会负责容器的正确启动顺序
- 定义新的dnmonster容器。来源于amouat/dnmonster:1.0
移除之前运行的所有容器,接着使用compose来重建和运行应用:
docker rm $(docker stop $(docker ps -q))
docker-compose build
docker compose up -d
更新identicon.py内容如下:
from flask import Flask, Response, request
import requests
import hashlib
app = Flask(__name__)
salt = "UNIQUE_SALT"
default_name = 'Joe Bloggs'
@app.route('/', methods=['GET', 'POST'])
def mainpage():
name = default_name
if request.method == 'POST':
name = request.form['name']
salted_name = salt + name
name_hash = hashlib.sha256(salted_name.encode()).hexdigest()
header = '<html><head><title>Identidock</title></head><body>'
body = '''<form method="POST">
Hello <input type="text" name="name" value="{0}">
<input type="submit" value="submit">
</form>
<p>You look like a:
<img src="/monster/{1}"/>
'''.format(name, name_hash)
footer = '</body></html>'
return header + body + footer
@app.route('/monster/<name>')
def get_identicon(name):
r = requests.get('http://dnmonster:8080/monster/' + name + '?size=80')
image = r.content
return Response(image, mimetype='image/png')
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
- 导入对用户输入进行散列处理的程序库
- 定义散列函数的salt值,通过改变这个值,相同的输入在不同的网站可以生成不一样的identicon
- 表单提交是http post请求,所以必须给route声明加入methods命名的参数,明确宣告route可以处理post和get
- 如果method等同于post,并来自于点击提交按钮,需要用户赋值输入name
- 对输入执行SHA256算法,以获取散列值
- 用散列值改变图像url,在加载时候,调用get_identicon
3. 实现缓存功能
使用Redis实现缓存功能
from flask import Flask, Response, request
import requests
import hashlib
import redis
app = Flask(__name__)
cache = redis.StrictRedis(host='redis', port=6379, db=0)
salt = "UNIQUE_SALT"
default_name = 'Joe Bloggs'
@app.route('/', methods=['GET', 'POST'])
def mainpage():
name = default_name
if request.method == 'POST':
name = request.form['name']
salted_name = salt + name
name_hash = hashlib.sha256(salted_name.encode()).hexdigest()
header = '<html><head><title>Identidock</title></head><body>'
body = '''<form method="POST">
Hello <input type="text" name="name" value="{0}">
<input type="submit" value="submit">
</form>
<p>You look like a:
<img src="/monster/{1}"/>
'''.format(name, name_hash)
footer = '</body></html>'
return header + body + footer
@app.route('/monster/<name>')
def get_identicon(name):
image = cache.get(name)
if image is None:
print ("Cache miss", flush=True)
r = requests.get('http://dnmonster:8080/monster/' + name + '?size=80')
image = r.content
cache.set(name, image)
return Response(image, mimetype='image/png')
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
- 导入redis模块
- 建立redis缓存,通过docker连接的特性,让redis这个主机名能够被解析
- 检查名字中是否已经存在在缓存中
- 如果缓存未命中,返回None
- 输出一些调试信息,表示我们没有在缓存中找到图像
- 把图像添加到缓存中,并与名字关联
更新Dockerfile和docker-compose.yml的内容:
FROM python:3.4
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install Flask==0.10.1 uWSGI==2.0.8 requests==2.5.1 redis==2.10.3
WORKDIR /app
COPY app /app
COPY cmd.sh /
EXPOSE 9090 9191
USER uwsgi
CMD ["/cmd.sh"]
FROM python:3.4
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install Flask==0.10.1 uWSGI==2.0.8 requests==2.5.1 redis==2.10.3
WORKDIR /app
COPY app /app
COPY cmd.sh /
EXPOSE 9090 9191
USER uwsgi
CMD ["/cmd.sh"]
identidock:
build: .
ports:
- "5000:5000"
environment:
ENV: DEV
volumes:
- ./app:/app
links:
- dnmonster
- redis
dnmonster:
image: amouat/dnmonster:1.0
redis:
image: redis:3.0
现在,如果你先用docker-compose stop停止identicon,可以用docker-compose build和docker-compose up来启动新的版本。
4.微服务
我们的identicon应用由3个容器组成,其中一个用python的web程序,它与一个用javascript写成的服务,以及一个用c语言实现的键值数据库通讯。后面的章节将介绍如何使用少量的工作来使得更多的微服务接到identicon,包括第九章的反向代理,第十章的监控和日志记录方案。
5.总结
现在我们的应用已经基本可用,虽然很简单,但是功能有了,我们已经了解如何重复使用现有的镜像。更重要的是,我们还看到了容器如何自然演变成一组具有明确功能的小服务,它们可以交互形成一个更大的系统。这就是微服务框架。