Python

Python

用Python/Keras/Flask/Docker在Kubernetes上部署深度学习模型

hokingyang 发表了文章 • 0 个评论 • 3443 次浏览 • 2018-10-29 10:15 • 来自相关话题

# 简单到老板也可以亲自部署 这篇博文演示了如何通过Docker和Kubernetes,用Keras部署深度学习模型,并且通过Flask提供REST API服务。 这个模型并不是强壮到可供生产的模型,而是给Kubernet ...查看全部
# 简单到老板也可以亲自部署
这篇博文演示了如何通过Docker和Kubernetes,用Keras部署深度学习模型,并且通过Flask提供REST API服务。

这个模型并不是强壮到可供生产的模型,而是给Kubernetes新手一个尝试的机会。我在Google Cloud上部署了这个模型,而且工作的很好。另外用户可以用同样的步骤重现以上功能。如果用户担心成本,Google提供了大量免费机会,这个演示基本没有花钱。
# 为什么用Kubernetes来做机器学习和数据科学
Kubernetes以及Cloud Native,正在席卷整个世界,我们已经感受到了。我们正处在一个由AI/Big Data/Cloud驱动的技术风暴中心,Kubernetes也正在加入这个中心。

但是如果从数据科学角度看并没有使用Kubernetes的特殊原因。但是从部署,扩展和管理REST API方面来看,Kubernetes正在实现简易化的特性。
# 步骤预览

  1. 在Google Cloud上创建用户
  2. 使用Keras/Flask/Docker搭建一个REST API的机器学习模型服务
  3. 用Kubernetes部署上述模型
  4. enjoy it

## 步骤一:在Google Cloud上创建用户
我在Google Compute Engine上创建了一个对外提供服务的容器化深度学习模型,当然Google平台并不是必须的,只要能够安装Docker,随便选择平台模式。
1.png

进入Google云平台,点击左侧屏幕选择Compute Engine,启动Google Cloud VM。然后选择“Create Instance”,可以看到已经运行的实例。
2.png

下一步选择计算资源。默认设置就足够,因为只是演示,我选择了4vCPUs和15G内存。
3.png

选择操作系统和磁盘大小。我选择了CentOS 7,100G硬盘。建议磁盘大于10G,因为每个Docker容器有1G大小。
4.png

最后一步是配置允许HTTP/S工作的防火墙策略。建议选择全部透明,以便减少麻烦。
5.png

选择“Create”,一切进展顺利。
6.png

## 步骤二:用Keras创建深度学习模型
SSH登录到虚机开始建立模型。最简单方式就是点击虚机下方的SSH图标,会在浏览器中打开一个终端。
7.png

1、删除预装Docker
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine


2、安装最新Docker版本
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager — add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce

3、启动容器运行测试脚本
sudo systemctl start docker
sudo docker run hello-world

以下是正确输出:
Hello from Docker!
This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal

4、创建深度学习模型
这里会借用Adrian Rosebrock的一个脚本,他提供了使用Keras的深度学习模型并通过Flask提供服务的教程,可以从这里访问。

这个模型可以直接执行。但是我修改了两个配置信息:

首先,改变了容器配置,默认flask使用127.0.0....作为默认服务地址,这会在容器内部运行时出现问题。我将它修改成0.0.0.0,这样就可以实现对外和对内都可以工作的IP地址。

第二是关于Tensorflow的配置,可以从GitHub中找到这个问题描述
global graph
graph = tf.get_default_graph()
...
with graph.as_default():
preds = model.predict(image)

运行脚本,首先创建专用目录:
mkdir keras-app
cd keras-app

创建app.py文件:
`vim app.py`
# USAGE
# Start the server:
# python app.py
# Submit a request via cURL:
# curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict'

# import the necessary packages
from keras.applications import ResNet50
from keras.preprocessing.image import img_to_array
from keras.applications import imagenet_utils
from PIL import Image
import numpy as np
import flask
import io
import tensorflow as tf

# initialize our Flask application and the Keras model
app = flask.Flask(__name__)
model = None

def load_model():
# load the pre-trained Keras model (here we are using a model
# pre-trained on ImageNet and provided by Keras, but you can
# substitute in your own networks just as easily)
global model
model = ResNet50(weights="imagenet")
global graph
graph = tf.get_default_graph()

def prepare_image(image, target):
# if the image mode is not RGB, convert it
if image.mode != "RGB":
image = image.convert("RGB")

# resize the input image and preprocess it
image = image.resize(target)
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
image = imagenet_utils.preprocess_input(image)

# return the processed image
return image

@app.route("/predict", methods=["POST"])
def predict():
# initialize the data dictionary that will be returned from the
# view
data = {"success": False}

# ensure an image was properly uploaded to our endpoint
if flask.request.method == "POST":
if flask.request.files.get("image"):
# read the image in PIL format
image = flask.request.files["image"].read()
image = Image.open(io.BytesIO(image))

# preprocess the image and prepare it for classification
image = prepare_image(image, target=(224, 224))

# classify the input image and then initialize the list
# of predictions to return to the client
with graph.as_default():
preds = model.predict(image)
results = imagenet_utils.decode_predictions(preds)
data["predictions"] = []

# loop over the results and add them to the list of
# returned predictions
for (imagenetID, label, prob) in results[0]:
r = {"label": label, "probability": float(prob)}
data["predictions"].append(r)

# indicate that the request was a success
data["success"] = True

# return the data dictionary as a JSON response
return flask.jsonify(data)

# if this is the main thread of execution first load the model and
# then start the server
if __name__ == "__main__":
print(("* Loading Keras model and Flask starting server..."
"please wait until server has fully started"))
load_model()
app.run(host='0.0.0.0')

5、创建requirements.txt文件

为了在容器内运行代码,需要创建requirements.txt文件,其中包括需要运行的包,例如keras、flask、一起其它相关包。这样无论在哪里运行代码,依赖包都保持一致。
keras
tensorflow
flask
gevent
pillow
requests

6、创建Dockerfile
FROM python:3.6
WORKDIR /app
COPY requirements.txt /app
RUN pip install -r ./requirements.txt
COPY app.py /app
CMD ["python", "app.py"]~

首先让容器自行下载Python 3安装image,然后让Python调用pip安装requirements.txt中的依赖包,最后运行python app.py。
7、创建容器
sudo docker build -t keras-app:latest .

在keras-app目录下创建容器,后台开始安装Python 3 image等在步骤6中定义的操作。

8、运行容器
sudo docker run -d -p 5000:5000 keras-app

用`sudo docker ps -a`检查容器状态,应该看到如下输出:
[gustafcavanaugh@instance-3 ~]$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d82f65802166 keras-app "python app.py" About an hour ago Up About an hour 0.0.0.0:5000->5000/tcp nervous_northcutt

9、测试模型

现在可以测试此模型。用狗的照片作为输入,可以返回狗的品种。在Adrian的示例中都有详细代码和图片,我们也使用它们,并保存自工作目录下,命名为dog.jpg。
1_FJWlc5VIb2k5Y64DgMZuww.jpeg

执行命令:
curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict'

应该得到如下输出:
 {"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true} 

可以看到此模型成功将狗归类为比格犬。下一步,我们用Kubernetes部署容器模型。
## 第三步:用Kubernetes部署模型
1、创建Docker Hub账号

第一步需要在Docker hub上传模型,以便使用Kubernetes集中管理。

2、登录到Docker Hub

`sudo docker login`, 登录到Docker Hub,应该看到如下输出:
Login Succeeded


3、给容器打标签

给模型容器命名,上传前先给它打标签。

`sudo docker images`,应该得到容器的id,输出如下:
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE keras-app           latest              ddb507b8a017        About an hour ago   1.61GB

打标签命令如下:
#Format
sudo docker tag /
#My Exact Command - Make Sure To Use Your Inputs
sudo docker tag ddb507b8a017 gcav66/keras-app

4、将模型容器上传到Docker Hub

运行命令如下:
#Format
sudo docker push /

#My exact command
sudo docker push gcav66/keras-app

5、创建Kubernetes集群

在Google Cloud Home界面,选择Kubernetes Engine。
8.png

创建新集群:
9.png

选择集群内节点资源,因为要启动三个节点(每个节点4vCPU和15G内存),至少需要12vCPU和45G内存。
10.png

连接集群,Google’s Kubernetes自动会在VM上安装Kubernetes。
11.png

在Kubernetes中运行容器:
kubectl run keras-app --image=gcav66/keras-app --port 5000

确认是否Pod正确运行`kubectl get pods`,输出如下:
gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get pods
NAME READY STATUS RESTARTS AGE
keras-app-79568b5f57-5qxqk 1/1 Running 0 1m

为了安全起见,将服务端口暴露与80端口:
kubectl expose deployment keras-app --type=LoadBalancer --port 80 --target-port 5000

确认服务正常启动:`kubectl get service`,正常输出如下:
gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
keras-app LoadBalancer 10.11.250.71 35.225.226.94 80:30271/TCP 4m
kubernetes ClusterIP 10.11.240.1 443/TCP 18m


提取cluster-IP,并将其合并于服务提交命令:`curl -X POST -F image=@dog.jpg 'http:///predict'`,得到正常输入如下:
$ curl -X POST -F image=@dog.jpg 'http://35.225.226.94/predict'
{"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true}

## 第四步:总结
本文提供了一个使用Keras和Flask提供REST API服务的深度学习模型,并把它集成到容器内部,上传到Docker Hub,并用Kubernetes部署,非常容易地实现了对外提供服务和访问。

现在,我们可以对这个项目进行很多改进。对于初学者,可以改变本地Python服务到更加强壮的gunicorn;可以横向扩展Kubernetes,实现服务扩容;也可以从头搭建一套Kubernetes环境。

原文链接:Deploy Your First Deep Learning Model On Kubernetes With Python, Keras, Flask, and Docker(翻译:杨峰)

用户直接运行代码库源文件( Python 单体应用)会有什么弊端?

回复

mowangmm 发起了问题 • 1 人关注 • 0 个回复 • 1376 次浏览 • 2018-03-14 17:04 • 来自相关话题

技术漫谈 | 使用docker-compose进行python开发

wise2c 发表了文章 • 0 个评论 • 4047 次浏览 • 2017-07-11 17:36 • 来自相关话题

作者:郑云龙 Docker提供了容器级别的资源隔离。由于Python的外部依赖管理中存在的问题,我们通常会使用virtualenv来对不同的项目创建其唯一的依赖环境。这时利用Docker进行Python开发,可以轻松解决不同Pytho ...查看全部
作者:郑云龙

Docker提供了容器级别的资源隔离。由于Python的外部依赖管理中存在的问题,我们通常会使用virtualenv来对不同的项目创建其唯一的依赖环境。这时利用Docker进行Python开发,可以轻松解决不同Python项目之间的依赖隔离问题。

作为应用程序,我们通常需要依赖于多种外部服务,比如数据库、缓存服务等等。Docker-compose就是在Docker容器的基础上,提供了统一的容器编排语言,可以让你更轻松的利用Docker构建你的应用环境。

编写Dockerfile

我们使用requirements.txt定义我们的第三方python包依赖

1微信截图_20170710112612.png


Project-Root
|– static
|– templates
|– server.py
|– requirements.txt
|– Dockerfile
|– docker-compose.yml

编写Dockerfile内容如下:

2微信截图_20170710113234.png


在Dockerfile中,我们主要目的:通过requirements.txt文件安装第三方的Python库依赖;利用Docker的容器隔离,可以忽略掉很多在本地开发中需要使用的东西,比如virtualenv。

编排我们的Docker容器

在案例中,应用程序依赖了mongodb作为数据存储服务,以及redis作为缓存服务。在一般情况下,作为开发团队要么我们搭建统一的mongodb;要不就每个人在开发机上单独部署。

而在Docker中,我们则不在需要做这么多无用的事情。 Docker官方提供了大量的基础容器,基本涵盖了日常开发中我们需要的大部分依赖。 在https://hub.docker.com/我们可以搜索到我们需要的基础镜像。

比如mongodb以及redis,在docker-hub上官方都提供了容器话的服务。

以redis容器为例,我们在本地搭建redis服务要做的事情主要包括两步:


3微信截图_20170710144513.png



这个时候我们就可以通过访问0.0.0.0:63775来访问我们的redis服务器了。

我们也可以通过Docker原生的命令来连接我们的应用容器和redis容器,以使我们的代码能够正常的访问redis服务

4微信截图_20170710144857.png


而事实上,我们可以使用更加简化的方式来定义我们的容器组合管理,使用Docker-compose(前身Fig)来定义我们的容器组合关系。

5微信截图_20170710145026.png


这里我们定义了3个容器web、redis、mongo。 其中,web容器是通过当前目录的Dockerfile进行构建,同时将当前目录挂在到/app目录。 而redis和mongo则直接使用官方进行。

通过使用links,我们可以在web容器中通过 ‘redis:6375’以及’mongo:21707’直接访问相应的服务。

开始Coding吧

6微信截图_20170710145425.png


Docker会根据当前的目录下得Dockerfile构建基础镜像,并且使用python server.py运行程序,并且运行redis以及mongo服务。

同时由于使用了volumes挂载了本地目录到/app,此时如果我们是开启的Debug模式,我们就可以直接在本地使用你喜欢的文本编辑器去编写代码,并且更新的代码能够实时被重新加载。

当然在使用Docker中最漫长的过程就是,下镜像,下镜像&下镜像。

文末福利:请大家关注'Wise2C'公众号并回复【进群】,睿云小助手会第一时间拉你进入
【Docker企业落地实践群】,我们分享的各个企业案例项目的技术专家与用户代表,正在敬候您的光临,期待大家就项目的更多细节与疑问与群里的大牛们进行咨询探讨。
若需要了解更多有关Wise系列PaaS产品的详情,请与我们的市场团队联系: contact@wise2c.com

睿云智合微信截图_20170926111623.png


面面观 | 使用python 连接数据库,插入并查询数据--link

enncloud 发表了文章 • 0 个评论 • 1292 次浏览 • 2017-06-02 16:01 • 来自相关话题

1,将两个docker 连接起来 首先需要搭建环境: 在alpine下面创建mariadb数据库: http://blog.csdn.net/freewebsys/article/details/53540 ...查看全部
1,将两个docker 连接起来

首先需要搭建环境:
在alpine下面创建mariadb数据库:
http://blog.csdn.net/freewebsys/article/details/53540615
用户名密码是root。
然后创建http的Python环境:
http://blog.csdn.net/freewebsys/article/details/53509676
接下来做一个简单数据查询和插入操作。

2,python代码:
main.py
#!/usr/bin/python# -- coding: utf-8 --from flask import Flaskimport MySQLdb app = Flask(__name__) mysql_host = "mysql"mysql_user = "root"mysql_pwd = "root"mysql_db_name = "demo"""" CREATE DATABASE `demo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; CREATE TABLE `demo`.`user_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; """def query_db(sql): db = MySQLdb.connect(mysql_host, mysql_user, mysql_pwd, mysql_db_name) cur = db.cursor() try: cur.execute(sql) result = cur.fetchall() except: print("error: sql:" + sql) cur.close() db.close() return resultdef update_db(sql): print(sql) db = MySQLdb.connect(mysql_host, mysql_user, mysql_pwd, mysql_db_name) cur = db.cursor() try: cur.execute(sql) except: print("error: sql:" + sql) db.commit() cur.close() db.close()@app.route("/list")def list(): results = query_db(" select id,name from `demo`.`user_info` ") out = "results:\n" for result in results: id = result[0] name = result[1] out += "id:" + str(id) + ",name:" + name +"\n" return out@app.route("/add")def add(): sql = " insert ignore into `demo`.`user_info`(`name` ) values ('zhangsan') " update_db(sql) return "ok"if __name__ == "__main__": app.run(host='0.0.0.0', port=5000)


代码和之前的http没有太大区别,只是增加了数据库的查询和插入操作。
一共就有两个url,一个list,查询全部数据,一个add,写死增加。

3,创建数据库表
MySQL需要创建下数据库和表:

CREATE DATABASE `demo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;CREATE TABLE `demo`.`user_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`name` varchar(200) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这个user_info表一共就有两个字段,一个id自增,一个是name字符串的。
当然这个数据库不在本地,是另外的一个Docker 容器。
http在一个容器上面。

4,使用link连接起来
数据创建名称叫mariadb。
跑http。

docker run -d -p 5000:5000 --name py-http --link mariadb:mysql demo/py-http:1.0

特别注意这里的–link 容器名:昵称,然后对于py-http容器来说mysql就是昵称了。
可以直接看下evn环境:

# docker exec -it py-http
bashbash-4.3# env
HOSTNAME=db7f7aba7c2f
MYSQL_ENV_MYSQL_ROOT_PASSWORD=root
MYSQL_ENV_MARIADB_VERSION=10.1.19+maria-1~jessie
MYSQL_ENV_GOSU_VERSION=1.7
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_ENV_MARIADB_MAJOR=10.1
MYSQL_PORT_3306_TCP=tcp://172.17.0.2:3306
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
TZ=Asia/Shanghai
SHLVL=1HOME=/root
MYSQL_NAME=/py-http/mysql
MYSQL_PORT_3306_TCP_PROTO=tcp
MYSQL_PORT_3306_TCP_ADDR=172.17.0.2
MYSQL_PORT=tcp://172.17.0.2:3306
_=/usr/bin/env

可以看到,在py-http容器下面已经把mariadb容器的环境变量直接引入了。
并且查看hosts:
# cat /etc/hosts
127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 mysql 48bd5fbf3ddc mariadb
172.17.0.3 db7f7aba7c2f

可以看到有了mysql变量的host了。
在外部访问:就说明测试成功。数据库能插入查询了。

# curl http://127.0.0.1:5000/add
ok[root@localhost http]# curl http://127.0.0.1:5000/list
results:
id:1,name:zhangsan
id:2,name:zhangsan

4,总结

docker设计的挺好的,每一个容器虽然独立,但是还可以连接起来。
这样组成一个微服务的集群。通过环境变量,把代码和环境分离。
保证测试环境,开发环境,线上环境的的一致。
极大的方便了开发运维,link 命令也非常好使。
新智云官网www.enncloud.cn

有没有image 包含各种数据库的驱动

回复

productive 发起了问题 • 1 人关注 • 0 个回复 • 1453 次浏览 • 2017-04-17 10:56 • 来自相关话题

Docker环境下运行Python + Selenium + Chrome

herryliq 发表了文章 • 0 个评论 • 9726 次浏览 • 2017-04-07 23:40 • 来自相关话题

Docker运行时占用的资源非常少,而且能将环境进行有效的隔离,可以快速的进行部署,因此可以将Docker与Selenium结合实现在容器中执行无界面的自动化操作。例如:自动测试、自动下载邮件等。 【深圳站|3天烧脑式Kubernet ...查看全部
Docker运行时占用的资源非常少,而且能将环境进行有效的隔离,可以快速的进行部署,因此可以将Docker与Selenium结合实现在容器中执行无界面的自动化操作。例如:自动测试、自动下载邮件等。

【深圳站|3天烧脑式Kubernetes训练营】培训内容包括:Kubernetes概述和架构、部署和核心机制分析、进阶篇——Kubernetes调工作原理及源码分析等。
#需求说明
通过Selenium自动的登录邮箱,下载邮箱中符合条件的邮件,并对邮件的内容进行解析存档。
#方案选择
Selenium官方提供了基于selenium hub的方式来管理Selenium的node节点,提供了分布式的远程调度方案,可以为SeleniumGrid添加各种类型的WebDriver。
selenium-hub.jpg

基于Selenium Grid的方案适用于以下场景:

  1. 通过Selenium自动访问网页时,需要阻塞等待与用户的交互,例如需要输入短信验证码的场景,通过Selenium Grid的调度,可以最优的利用所有可访问的资源,提高系统的并发执行效率。
  2. 需要对网页的兼容性进行测试时,可以接入多种内核的WebDriver到Selenium Grid中,这样在进行网页的自动化测试时,也同时对浏览器的兼容性进行了测试。
  3. 访问只支持IE内核的网站时,例如一些开发比较早的政府网站等。
  4. 访问需要安装安全控件的网站,例如网银的登录等。

本次的需求对时效性要求不高,邮件的获取需要由定时器进行触发,如果使用Selenium Grid,可能会增加了整个系统的复杂程度,分布式系统中每引入一个中间件,就会增加系统的复杂性,系统的可用性就会随之降低。

基于以上分析,考虑采用selenium handless的模式,在Docker中运行Chrome的WebDriver,完成最终邮件的自动获取。

在确定方案之后,需要寻找合适的Docker镜像,最终找到了chromium-xvfb这个镜像文件,集成了Chromium和Xvfb,可以满足在Docker容器中通过Selenium来进行无界面的操作。
#方案验证
1.下载chromium-xvfb的镜像文件
[root@prod ~]# docker pull markadams/chromium-xvfb-py2

2.交互模式运行
[root@prod ~]# docker run --rm -it markadams/chromium-xvfb-py2 bash

3.进入python环境
root@8016c66877aa:/usr/src/app# python

4.输入以下python代码
from selenium import webdriver
driver = webdriver.Chrome()
url = 'https://github.com/mark-adams/docker-chromium-xvfb/blob/master/samples/
python3/test_google.py'
driver.get(url)

没有错误输出,证明Chrome已经在Docker下可以正常工作。
#方案实施
以markadams/chromium-xvfb-py2作为基础镜像,重新编写的Dockerfile如下:
FROM markadams/chromium-xvfb-py2
WORKDIR /opt/zs5s/download-mail
COPY ./download-mail/.pip /root/.pip/
COPY ./download-mail/requirements.txt /opt/zs5s/download-mail/requirements.txt

RUN pip install --upgrade pip

RUN pip install -r requirements.txt && mkdir /tmp/downloaded_files

ENV DISPLAY :1
COPY ./download-mail /opt/zs5s/download-mail
COPY ./data_service /opt/zs5s/data_service
COPY ./save_mail /opt/zs5s/save_mail
COPY ./common/ /opt/zs5s/common
USER root

在编译运行后,发现在这个Docker容器内单纯的去访问网页没有问题,如果去执行带有键盘的操作时,会报错,错误信息如下:
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: unknown error: an X display is required for keycode conversions, consider using Xvfb
(Session info: chrome=57.0.2987.98)
(Driver info: chromedriver=2.28.455506 (18f6627e265f442aeec9b6661a49fe819aeeea1f),platform=Linux 4.4.27-moby x86_64)

经过调查是需要在启动容器时执行Xvfb,申请一块Screen,具体可以参考链接1。在Dockerfile中加入启动脚本:
CMD sh start.sh
start.sh的内容如下:

#!/bin/sh
export DISPLAY=:1
Xvfb $DISPLAY -ac -screen 0 1280x1024x8 &
sleep 1
ps -aux
tail -f start.sh #测试用,为了阻塞住容器内的进程

#结果验证
在新生成的容器内执行以下的Python脚本,可以正常执行,说明在Chrome中已经可以接收通过Selenium传入的键盘事件。
from splinter import Browser
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
url = 'https://github.com/mark-adams/docker-chromium-xvfb/blob/master/samples/python3/test_google.py'
browser = Browser('chrome')
browser.visit(url)
element = browser.find_by_xpath('/html/body/div[1]/header/div/div/div/div/form/label/input[1]')
element.type('aaaa')

#残留问题
在Python退出Selenium时,虽然执行了driver.quit()函数,但是实际上Chrome并没有真正的退出,似乎是Selenium的一个Bug,在3.1中提到了修正,但是好像没有起作用。

解决办法可以考虑执行shell脚本主动的kill掉Chrome的进程。
#参考链接

  1. protractor-sendkeys-not-working-an-x-display-is-required-for-keycode-conversion
  2. crawling-python-selenium-docker
  3. docker-chromium-xvfb

使用Rancher-Gen动态更新配置文件

Rancher 发表了文章 • 1 个评论 • 2627 次浏览 • 2017-01-25 09:44 • 来自相关话题

Docker和Rancher让大家能更轻易地部署和管理基于微服务的应用程序。然而,如果有些服务是依赖于其他动态服务的,那该如何管理它们的配置?你是否时常觉得,要是有方法能自动检测后端服务的变化,并实现配置文件的动态更新就好了?本文给你答案。 ...查看全部
Docker和Rancher让大家能更轻易地部署和管理基于微服务的应用程序。然而,如果有些服务是依赖于其他动态服务的,那该如何管理它们的配置?你是否时常觉得,要是有方法能自动检测后端服务的变化,并实现配置文件的动态更新就好了?本文给你答案。

前言

Docker和Rancher让大家能更轻易地部署和管理基于微服务的应用程序。然而,有一个关键的挑战是,如果有些服务是依赖于其他动态服务的,那该如何管理它们的配置?

试想以下情形:您有多个运行Web应用程序的后端容器,和一些将所有的请求都代理到这些后端容器的nginx容器。现在,你必须要部署一个新版本的Web应用程序,这意味着你需要构建和部署新版本的后端容器。在这些部署工作完成之后,nginx的配置需要更改为指向新的后端容器。那么,你该怎样处理nginx呢?改变其配置,构建一个新的容器并部署它?此刻的你是不是会觉得,要是有方法能自动检测后端服务的变化,并实现nginx的动态更新就好了?

这就是Rancher-Gen要闪亮登场的时刻!

Rancher-Gen是一个Python的工具,它能监听的Rancher服务的变化,并呈现一个用户指定的Jinja2模板。这允许用户为现有的、并基于这些变化的服务生成配置文件。另外,Rancher-Gen提供了一种机制,在模板呈现之后会运行通知命令。下面的教程会介绍如何自动生成运行ghost博客平台后端服务的nginx配置文件 。

教程

下面介绍的所有配置文件都可以在Rancher-Gen库中的演示目录下找到。

第1步 – 部署Ghost服务

为简单起见,我们打算使用Docker hub中的官方ghost镜像。因此,创建一个docker-compose.yml 文件,并添加以下的ghost服务:


ghost:
image: ghost
expose:
- "2368"


现在,用Rancher Compose部署ghost服务:


$ rancher-compose -p demo up -d ghost


第2步 – 用Rancher-Gen创建nginx镜像

下面是用以搭建nginx镜像的Dockerfile:


FROM phusion/baseimage:0.9.17
MAINTAINER pitrho

# Step 1 - Install nginx and python
ENV DEBIAN_FRONTEND noninteractive
RUN \
apt-add-repository -y ppa:nginx/stable && \
apt-get update && \
apt-get install -y python-software-properties \
wget \
nginx \
python-dev \
python-pip \
libev4 \
libev-dev \
expect-dev && \
rm -rf /var/lib/apt/lists/* && \
chown -R www-data:www-data /var/lib/nginx && \
apt-get clean

# Step 2 - Install rancher-gen
ENV RANCHER_GEN_VERSION 0.1.2
RUN pip install rancher-gen==$RANCHER_GEN_VERSION

# Step 3 - Define services
RUN mkdir /etc/service/nginx /etc/service/rancher_gen /nginxconf
COPY nginx_run /etc/service/nginx/run
COPY rancher-gen_run /etc/service/rancher_gen/run
COPY default.j2 /nginxconf

# Step 4 - Use baseimage-docker's init system.
CMD ["/sbin/my_init"]

# Step 5 - Expose ports.
EXPOSE 80
EXPOSE 443


让我们来一步一步拆解Dockerfile。步骤1和2不言自明:只需安装nginx、Python和Rancher-Gen。

第3步要设置镜像启动时运行的服务。第一个服务是nginx,它用/etc/servce/nginx文件运行。该文件的内容是:


#!/bin/bash
rancher-gen --host $RANCHER_GEN_HOST \
--port $RANCHER_GEN_PORT \
--access-key $RANCHER_GEN_ACCESS_KEY \
--secret-key $RANCHER_GEN_SECRET_KEY \
--project-id $RANCHER_GEN_PROJECT_ID \
$RANCHER_GEN_OPTIONS \
--notify "service nginx reload" /nginxconf/default.j2 /etc/nginx/sites-available/default


注意一下在通知步骤之后,我们是怎样通过名为 /nginxconf/default.j2 和/etc/nginx/sites-available/default 的这两个路径的。前者是Jinjia2模板,后者是渲染模板的输出位置。以下是在default.j2文件的内容:


upstream ghost.backend {
{% for container in containers %}
{% if container['state'] == "running" %}
server {{container['primaryIpAddress']}}:2368;
{% endif %}
{% endfor %}
}

server {
listen 80;
server_name ghost_demo;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://ghost.backend;
proxy_redirect off;
}
}


Dockerfile的第4步和第5步在镜像中设置了运行命令“/sbin/my_init”并暴露端口80和443。

现在是时候构建镜像了:


$ docker build -t="pitrho/nginx-rancher-gen-demo" .


第3步 – 创建并部署nginx服务

现在我们已有了nginx镜像,就可以开始向我们在第1步中创建的docker-compose.yml文件中添加nginx服务了。


ghost:
image: ghost
expose:
- "2368"

nginx:
image: pitrho/nginx-rancher-gen-demo:latest
ports:
- 80:80
links:
- ghost
environment:
NGINX_RUN_TYPE: rancher-gen
RANCHER_GEN_HOST: $RANCHER_HOST
RANCHER_GEN_PORT: $RANCHER_PORT
RANCHER_GEN_ACCESS_KEY: $RANCHER_ACCESS_KEY
RANCHER_GEN_SECRET_KEY: $RANCHER_SECRET_KEY
RANCHER_GEN_PROJECT_ID: $RANCHER_GEN_PROJECT_ID
RANCHER_GEN_OPTIONS: --stack demo --service ghost


上述可变的RANCHER_GEN_OPTIONS环境是用于向Rancher-Gen传递附加命令行选项的 。你可以在Rancher-Gen文档中查看这些选项的说明。

现在运行rancher-compose来启动nginx服务:


$ rancher-compose -p demo up -d nginx


此时,ghost和nginx服务都启动并运行了:



而且,将浏览器指向运行中的nginx容器中主机的IP地址,你就可以访问ghost了:



如果你使用shell来检查nginx容器,并打开渲染的文件 /etc/nginx/sites-enabled/default,你将会看到以下的输出:


upstream ghost.backend {
server 10.42.136.216:2368;
}

server {
listen 80;
server_name ghost_demo;

location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://ghost.backend;
proxy_redirect off;
}
}


正如预期的那样,这是在运行rancher-gen命令时的基于指定模板的渲染输出。此时,如果你要升级ghost服务,并再次查看渲染文件,你会发现上游部分下的IP地址已经改变了。

结论

总结来说,Rancher-Gen是一个自动化工具,可用于生成文件,并运行通知命令。借助Jinja2的模板表现力,及其整洁的命令行界面,Rancher-Gen可用于生成大多数配置文件,并自动解决那些对大多数系统管理员和软件工程师而言繁琐和重复的工作。

原文来源:Rancher Labs

docker 反向工程

ozbillwang 发表了文章 • 2 个评论 • 3739 次浏览 • 2016-10-18 18:46 • 来自相关话题

今天参加了一个meetup,演讲者介绍了一个可以模拟docker 命令的工具,可以在不安装docker 的情况下,体验下载镜像,启动容器的功能。 这个工具使用python写的,如果有兴趣花些时间去理解一下,对docker 的深层次理解 ...查看全部
今天参加了一个meetup,演讲者介绍了一个可以模拟docker 命令的工具,可以在不安装docker 的情况下,体验下载镜像,启动容器的功能。

这个工具使用python写的,如果有兴趣花些时间去理解一下,对docker 的深层次理解会有很大的帮助。

# 运行环境

你需要一台linux 系统,本文用centos 7 , 暂时不支持windows 或者 mac os

# 卸载 docker engine

确保docker 命令 不存在。

# 准备环境

yum install -y gcc python-devel net-tools lsof 


# mocker工具的环境安装

$ git clone https://github.com/tonybaloney/mocker.git
$ cd mocker
$ pip install virtualenv
$ virtualenv ENV
$ source ENV/bin/activate
$ pip install -r requirements.txt
$ ./mocker.py help
Usage:
mocker pull []
mocker run
mocker images
mocker (-h | --help)
mocker --version


# 体验 pull 命令

$ ./mocker.py pull nginx
Starting new HTTPS connection (1): auth.docker.io
"GET /token?service=registry.docker.io&scope=repository:library/nginx:pull HTTP/1.1" 200 1442
Fetching manifest for nginx:latest...
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/manifests/latest HTTP/1.1" 200 6938
Fetching layer sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4..
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4 HTTP/1.1" 307 432
Starting new HTTPS connection (1): dseasb33srnrn.cloudfront.net
"GET /registry-v2/docker/registry/v2/blobs/sha256/a3/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4/data?Expires=1476787299&Signature=P0typ2UZrgFdGd4SrfBvKdKefSC8JkkNWxTExzZYTE6luS8R0ZbjCWWBHbwEj5v-q668Vf-y11WBULC7O~SWcYycly7ek0Eemlmr4eGphP-zdEJqCn9Nol~YSqrI4BX~MHFoWgIc9uXUJ1HM8VnTaMXIVELt5lNT9wDRz7OuzU8_&Key-Pair-Id=APKAJECH5M7VWIS5YZ6Q HTTP/1.1" 200 32
...
Fetching layer sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb..
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/blobs/sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb HTTP/1.1" 307 432
Starting new HTTPS connection (1): dseasb33srnrn.cloudfront.net
"GET /registry-v2/docker/registry/v2/blobs/sha256/6a/6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb/data?Expires=1476787301&Signature=d1jSz7Z9Syjlk9OPjzYgFk37f-g9d1OK--2fi4FdotZwbmFTLDrj~TyRFx9WZA10W7DUJ7vL-GFb5WjOIoHWe1CR2dm9NewjvzI-k7gI6CmPeG0F0ZRseaYzanmoYYSIUSPbn2hsbq57pl43i3pJ2NLKbGb2eGuL~YBgjUFsEq0_&Key-Pair-Id=APKAJECH5M7VWIS5YZ6Q HTTP/1.1" 200 51354364
[list]
[*]bin[/*]
[*]bin/bash[/*]
[*]bin/cat[/*]
[*]bin/chacl[/*]
[*]bin/chgrp[/*]
[*]bin/chmod[/*]
[*]bin/chown[/*]
[*]bin/cp[/*]
[*]bin/dash[/*]
[*]bin/date[/*]
[/list]...
Fetching layer sha256:2fbd37c8684bca3df2090b8b8acce020837d560ec8917f25714e45e7d1f4611e..
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/blobs/sha256:2fbd37c8684bca3df2090b8b8acce020837d560ec8917f25714e45e7d1f4611e HTTP/1.1" 307 432
Starting new HTTPS connection (1): dseasb33srnrn.cloudfront.net
"GET /registry-v2/docker/registry/v2/blobs/sha256/2f/2fbd37c8684bca3df2090b8b8acce020837d560ec8917f25714e45e7d1f4611e/data?Expires=1476787399&Signature=DNXOXyA9an018bG25GtQMaErpQwOtZUgMVW2Czur1DbwqJLe-w-5ETapnDVlz7WksCXNZ9JaO-hMBv~UjOOwQD1cjnpm3-QVMWGsnS4TBHLA9YZGx8wUMlUyQonSvHRTZKI2vr-SMlPDe91WgUzA-OrywSNvAXEqdIm-sn5qvPE_&Key-Pair-Id=APKAJECH5M7VWIS5YZ6Q HTTP/1.1" 200 195
[list]
[*]var[/*]
[*]var/log[/*]
[*]var/log/nginx[/*]
[*]var/log/nginx/access.log[/*]
[*]var/log/nginx/error.log[/*]
[/list]...
Fetching layer sha256:20a0fbbae14864e06e14f89126551d004555d9e2c13591105862ca1f9a418e9d..
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/blobs/sha256:20a0fbbae14864e06e14f89126551d004555d9e2c13591105862ca1f9a418e9d HTTP/1.1" 307 432
Starting new HTTPS connection (1): dseasb33srnrn.cloudfront.net
"GET /registry-v2/docker/registry/v2/blobs/sha256/20/20a0fbbae14864e06e14f89126551d004555d9e2c13591105862ca1f9a418e9d/data?Expires=1476787401&Signature=H-52MjpIcEpNWHyikqDB50rrv1nj-4wPON6jW0gK5OeLlguxAv2iSUZpnQ1ImL-ixTxhD0iLdCpzMNDLsZ2lagJOVM6Susd1Jn-l7N8EgXUBkwQWAejbsJTjV89O6cI7T60OzaWsBPQCbM2jPYyFsfMPCjb8jLahSGuu95Wy2iw_&Key-Pair-Id=APKAJECH5M7VWIS5YZ6Q HTTP/1.1" 200 20134306
[list]
[*]etc[/*]
[*]etc/alternatives[/*]
[*]etc/alternatives/rename[/*]
[*]etc/alternatives/rename.1.gz[/*]
[*]etc/apt[/*]
[*]etc/apt/sources.list[/*]
[*]etc/apt/trusted.gpg[/*]
[*]etc/apt/trusted.gpg.d[/*]
[*]etc/apt/trusted.gpg~[/*]
[*]etc/ca-certificates[/*]
[/list]...


可以看到,其进程是: 授权,获得镜像清单 ($HOME/mocker/library_nginx.json),获取每层镜像。我们也可以看到docker 镜像服务器使用了aws 的 CDN 服务来加速下载。

在pull 镜像的时候,该作者给出很多有用的信息。 给出具体的api 命令及链接,每个docker 层的sha256码,每层的文件列表,等等

下载的镜像层文件(tar 包)被存放在 $HOME/mocker 目录下了。 并且里面的文件全部展开。

 tree $HOME/mocker/library_nginx


# 列出镜像

 $ ./mocker.py images
+---------------------+---------+---------+--------------------------+
| name | version | size | file |
+---------------------+---------+---------+--------------------------+
| library/hello-world | latest | 1006.0B | library_hello-world.json |
| library/nginx | latest | 68.2MiB | library_nginx.json |
+---------------------+---------+---------+--------------------------+


# 运行 容器

    $ python mocker.py run library/nginx
Creating cgroups sub-directories for user vagrant
Hierarchies availables: ['hugetlb', 'perf_event', 'freezer', 'cpuset', 'net_cls', 'memory', 'blkio', 'cpuacct', 'cpu', 'cpu,cpuacct', 'devices', 'systemd']
cgroups sub-directories created for user vagrant
Creating cgroups sub-directories for user root
Hierarchies availables: ['hugetlb', 'perf_event', 'freezer', 'cpuset', 'net_cls', 'memory', 'blkio', 'cpuacct', 'cpu', 'cpu,cpuacct', 'devices', 'systemd']
cgroups sub-directories created for user root
Running "nginx -g "daemon off;""
Creating cgroups sub-directories for user root
Hierarchies availables: ['hugetlb', 'perf_event', 'freezer', 'cpuset', 'net_cls', 'memory', 'blkio', 'cpuacct', 'cpu', 'cpu,cpuacct', 'devices', 'systemd']
cgroups sub-directories created for user root
Setting ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Setting ENV NGINX_VERSION=1.11.5-1~jessie


# 启动网络

另开一个窗口:

$ ip netns
netns_c_9052 (id: 0)

$ ip netns exec netns_c_9052 ifconfig
lo: flags=73 mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10
loop txqueuelen 0 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

veth1_c_9052: flags=4163 mtu 1500
inet 10.0.0.103 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::42:acff:fe11:90 prefixlen 64 scopeid 0x20
ether 02:42:ac:11:00:90 txqueuelen 1000 (Ethernet)
RX packets 8 bytes 648 (648.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8 bytes 648 (648.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

$ ip netns exec netns_c_9052 lsof -naP -i tcp:80
lsof: no pwd entry for UID 104
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 12006 root 26u IPv4 34170 0t0 TCP *:80 (LISTEN)
lsof: no pwd entry for UID 104
nginx 12007 104 26u IPv4 34170 0t0 TCP *:80 (LISTEN)



到这一步,可以看到,80 端口已经启用。

# 确认容器里的web服务运行

$ ip netns exec netns_c_9052 curl localhost:80



Welcome to nginx!



Welcome to nginx!


If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.



For online documentation and support please refer to
nginx.org.

Commercial support is available at
nginx.com.



Thank you for using nginx.





# 参考

repo: https://github.com/tonybaloney/mocker

ip-netns 命令参考 : http://man7.org/linux/man-pages/man8/ip-netns.8.html

如何在Python中使用ZeroMQ和Docker构建微服务架构

Leo_li 发表了文章 • 0 个评论 • 14693 次浏览 • 2016-02-28 18:43 • 来自相关话题

@Container容器技术大会将于6月4日在上海光大会展中心国际大酒店举办,来自携程、PPTV、蚂蚁金服、京东、浙江移动、海尔电器、唯品会、eBay、道富银行、麻袋理财等公司的技术负责人将带来实践经验分享,3月21日之前购票只需238元,欢迎感兴趣的同学抢购 ...查看全部
@Container容器技术大会将于6月4日在上海光大会展中心国际大酒店举办,来自携程、PPTV、蚂蚁金服、京东、浙江移动、海尔电器、唯品会、eBay、道富银行、麻袋理财等公司的技术负责人将带来实践经验分享,3月21日之前购票只需238元,欢迎感兴趣的同学抢购。
#微服务是什么?
微服务是一种架构风格,它包括多个彼此间进行通信的独立进程。在设计上,这些进程具有高度的可扩展性、相互解耦而且一次只完成一个较小的任务。这些服务都拥有自己的资源以及通过网络实现彼此间通信的进程。

相比于靠后端的单体结构来封装所有服务器逻辑的传统客户端-服务器架构(C/S架构)而言,微服务架构的差异性体现在关注点分离(Separation of concern)。这种设计模式更易于维护,使得灵活性、可扩展性及容错能力更强。但是这种分布式架构所的不足之处体现在如果设计不合理就会使得排错及维护变得复杂。
#一个简单微服务的例子
让我们来分析这样的一个场景:你正在使用微服务模式构建一个电子商务网店。

对于一个电商网店上的常见商品,好比说iPhone,其详情页会显示:

  • 产品的及基本信息
  • 你的购买历史
  • 哪些人买了iPhone也买了手机套
  • 与苹果手机相关的优惠和折扣
  • 店家的数据
  • 送货方式
  • 推荐商品等等
此外,这个简单的产品详情页的接口将有多个版本的来匹配Web、移动端以及用于第三方应用程序的REST API。在微服务模式中数据分布在多个服务之间。在这个例子中,服务包括:
  • 产品详情服务
  • 商家服务
  • 支付服务
  • 优惠及折扣服务
  • 库存服务
  • 定价服务
  • 回顾服务
  • 推荐服务
这些独立的服务是如何被访问的呢?解决办法是使用一个API网管,它作为所有客户端的单一入口并且根据需求调用分布在整个基础架构中的特定微服务。以上模式的行业应用案例是NetFlix API网关,它具有支持不同设备的多个API客户端。你可以点击此处了解更多。##构建一个简单的微服务目前有很多方法可以用于构建你的微服务。在本文中我们将使用ZeroMQ来创建两个进程之间的通信。ZeroMQ提供了用于在套接字之上开发可扩展、分布式systed的构建块。它使用椭圆曲线密码体制(第四版)来实现安全性,并提供了即刻开启的通讯模式。关于ZMQ,还有很多优点。MQ即是针对异步工作而设计的线程化消息队列。谈论太多zeroMQ的内容已经超出了本文的范畴,你可以阅读使用zeromq以及zeromq用于分布式系统。我们要使用的另一个工具是Docker。本文假设读者对Docker已经有了基础的了解。ZeroMQ有很多种通讯模式,为了开始我们的工作,让我们用ZeroMQ和Flask来配置一个简单的PUB-SUB。下图展示了组件之间的关系和数据流。
microservices-docker-python-apcelent.png
  • 1&3 - 一个flask服务器运行在5000端口上而且其URL是`/downcase/`。该URL用来接受(GET)请求,而所有格式为的请求将收到回应:答谢字符将会转换为小写字符并返回。
  • 2 - 回应的消息也被发送给同一个容器中的ZMQ发布者(Publisher)
  • 4,5 - ZMQ订阅者(subscriber)持续监听并将来自ZMQ服务器的消息保存到名为`subscriber.log`的文件中

##创建服务器
首先看一下我们的Dockerfile
FROM ubuntu:14.04 
RUN apt-get update
RUN apt-get install -y --force-yes python python-dev python-setuptools software-properties-common gcc python-pip
RUN apt-get clean all

RUN pip install pyzmq

RUN pip install Flask

ADD zmqserver.py /tmp/zmqserver.py


# # Flask Port

EXPOSE 5000


# # Zmq Sub Server

EXPOSE 4444

CMD ["python","/tmp/zmqserver.py"]

我们选择Ubuntu 14.04作为容器操作系统。我们安装了基本的软件包。通过pip,我们安装pyzmq(zeromq的Python绑定)同时也安装了Flask。接着我们导出端口5000(flask服务器)和4444(发布者运行的端口)。此外,我们复制了包含所有flask及zeromq pythond代码的脚本文件`zmqserver.py`并运行它。

现在我们来看一下zmqserver.py的内容:
# server.py 
import time
import zmq

HOST = '127.0.0.1'
PORT = '4444'

_context = zmq.Context()
_publisher = _context.socket(zmq.PUB)
url = 'tcp://{}:{}'.format(HOST, PORT)

def publish_message(message):
try:
_publisher.bind(url)
time.sleep(1)
_publisher.send(message)

except Exception as e:
print "error {}".format(e)
finally: _publisher.unbind(url)

from flask import Flask
from flask import request
app = Flask(__name__)

@app.route("/downcase/", methods=['GET'])
def lowerString():

_strn = request.args.get('param')
response = 'lower case of {} is {}'.format(_strn, _strn.lower()) publish_message(response)
return response

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False)

ZMQ发布者运行在4444端口上。我们创建了一个context并且声明了URL。我们运行了flask app,它通过URL `/downcase/`把GET获得的参数`Param`转换成小写字符,这就是服务的应答。应答的字符串是`published`,它作为一个消息把相同的字符串返回给浏览器。

为了构建以上的Docker映像(image),我们执行以下的命令:
`sudo docker build -t docker-zmq-pub`
并且在该映像之上执行:
`docker run --name docker-pub-server -p 5000:5000 -p 4444:4444 -t docker-zmq-pub`。

我们把容器中的端口5000和4444映射到这台主机上,于是无论客户端在哪里,它们都可以订阅这个发布者。
##订阅者客户端
# client.py
import zmq
import sys
import time
import logging
import os

HOST = '127.0.0.1'
PORT = '4444'

logging.basicConfig(filename='subscriber.log', level=logging.INFO)


class ZClient(object):

def __init__(self, host=HOST, port=PORT):
"""Initialize Worker"""
self.host = host
self.port = port
self._context = zmq.Context()
self._subscriber = self._context.socket(zmq.SUB)
print "Client Initiated"

def receive_message(self):
"""Start receiving messages"""
self._subscriber.connect('tcp://{}:{}'.format(self.host, self.port))
self._subscriber.setsockopt(zmq.SUBSCRIBE, b"")

while True:
print 'listening on tcp://{}:{}'.format(self.host, self.port)
message = self._subscriber.recv()
print message
logging.info(
'{} - {}'.format(message, time.strftime("%Y-%m-%d %H:%M")))

if __name__ == '__main__':
zs = ZClient()
zs.receive_message()

我们声明了发布者的IP地址及端口,当前它运行在同一个的主机上因此地址是127开头。我们在URL `tcp://IP:PORT`上进行监听。一旦我们收到一个消息,就将其附上时间戳后记录到名为`subscriber.log`的文件中。运行客户端要做的所有工作就是执行 `python .py`。如果你在以上的架构上进行构建,它可以很好地充当近实时的日志聚合引擎。

我在Unbuntu主机上对以上的代码进行了测试。这里所用的代码保管在GitHub上。这是一个如何配置ZMQ、Docker和Python服务器的基础讲解,在我的下一片文章中我们会使用我们已经学习的东西构建简单的微服务。

希望这篇文章对你有所帮助。

原文链接:How to Setup Microservices Architecture in Python with ZeroMQ & Docker(翻译:李毅)

使用Docker部署Python应用的一些最佳实践

codesun 发表了文章 • 0 个评论 • 22630 次浏览 • 2015-02-06 00:23 • 来自相关话题

【编者的话】本篇文章源自作者团队在长期开发过程中总结的宝贵经验,其中Supervisor、Gunicorn以及Nginx更是在使用Python开发Web应用时最常用的软件,因此对于打算使用Docker部署Python应用的读者而言,这些最佳实践是很有参考价值。 ...查看全部
【编者的话】本篇文章源自作者团队在长期开发过程中总结的宝贵经验,其中Supervisor、Gunicorn以及Nginx更是在使用Python开发Web应用时最常用的软件,因此对于打算使用Docker部署Python应用的读者而言,这些最佳实践是很有参考价值。同时希望各位在日常实践过程中,也能将各自踩到过的“坑”以及宝贵的经验分享出来,大家共同进步!


我们可以使用Docker简单而高效的部署Python应用,同时,也有一些最佳实践来帮助我们愉快的完成部署。当然,也不是说这些最佳实践就是完成部署的唯一方式,只不过我们团队发现它们具有高可用性,并且容易维护。注意本文中大多数内容都只是代表我的立场,基于Docker的实现方式有很多,你可以随便选择。本文中我不会过多的介绍Volume,因为它可能需要一个单独的话题来解释。我们通常会使用Volume将源代码复制到容器中,而不是在每次运行时都重新构建。
## DEBIAN_FRONTEND
Docker用户应该都很熟悉这个环境变量,它告知操作系统应该从哪儿获得用户输入。如果设置为"noninteractive",你就可以直接运行命令,而无需向用户请求输入(译者注:所有操作都是非交互式的)。这在运行apt-get命令的时候格外有用,因为它会不停的提示用户进行到了哪步并且需要不断确认。非交互模式会选择默认的选项并以最快的速度完成构建。

请确保你只在Dockerfile中调用的RUN命令中设置了该选项,而不是使用ENV命令进行全局的设置,因为ENV命令在整个容器运行过程中都会生效,所以当你通过BASH和容器进行交互时,如果进行了全局设置那就会出问题。例子如下:

# 正确的做法 - 只为这个命令设置ENV变量
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python3
# 错误地做法 - 为接下来的任何命令都设置ENV变量,包括正在运行地容器
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get install -y python3

## requirements.txt
相比于基本的代码(codebase),应用的依赖很少发生变化,因此我们可以在Dockerfile中安装项目依赖,这也可以加快之后的构建速度(之后的构建只需要构建变更的代码)。Docker容器的层级构建可以缓存依赖安装的过程,所以之后的的构建速度会非常快,因为它不需要重新下载和构建依赖。
## 文件顺序
按照上面的思路(利用缓存)来推断,文件添加到容器的顺序至关重要。我们应该把频繁变更的文件放置到Dockerfile的下方,以便充分使用缓存来加速Docker的构建过程。例如,应用配置、系统配置和依赖都很少改变,我们就可以把它们放到Dockerfile的顶部。而源文件,比如路由文件、视图(views)和数据库代码会经常发生改变,所以我们就可以把它们放在Dockerfile的下方,注意是Docker配置命令(EXPOSE、ENV等)的下方。

另外,不要考虑如何将你的文件拷贝到Docker,它不会加快你的构建速度,因为大多数的文件根本不会用到,比如应用源文件。
## 应用密钥
之前我们一直不知道如何把应用密钥安全的传递给应用,后来我们发现可以使用`docker run`命令中的`env-file`参数。我们会把所有的密钥和配置都放在app_config.list文件中,然后通过这个文件交付给应用。具体如下:

docker run -d -t -—env-file app_config.list

这个方法允许我们简单地改变应用设置和密钥,而无需重建一个容器。

注意:请务必确保app_config.list在.gitignore文件的记录中,不然它不会被检录到源文件中。
## Gunicorn
我们使用Gunicorn作为容器内部的应用服务器,Gunicorn非常的稳定并且性能很好,它有非常多的配置选项,比如指定worker数量和类型(green threads、gevent等)的能力,你可以根据负载来调整应用,以获得最佳性能。

启动Gunicorn很简单:

# 安装
pip3 install gunicorn

# 运行服务器
gunicorn api:app -w 4 -b 127.0.0.1:5000

最后是在Nginx后面运行你的应用服务器,这样你可以进行负载均衡。
## supervisord
你是不是想过在容器中运行多个进程?我想Supervisord绝对是你的最佳辅助工具。假设我们想部署这样一个容器,它包含Nginx反向代理以及Gunicorn应用。你通过BASH脚本可能就能实现,但是让我们想更加简洁一点。

Supevisor是“一个进程控制系统,它支持用户在类UNIX操作系统上,监视并控制一些进程”。听起来很完美!你需要先在你的Docker容器内安装Supervisor。

RUN DEBIAN_FRONTEND=noninteractive apt-get install -y
supervisor

为了让Supervisor知道该运行什么以及如何管理进程,我们接下来需要为它写一个配置文件。

[supervisord]
nodaemon = true # 这个会让supervisord运行在前端

[program:nginx] # 你想运行的第一个程序的命名
command = /usr/sbin/nginx # nginx可执行程序的路径
startsecs = 5 # 如果nginx保持开启5s,我们视为启动成功

[program:app-gunicorn]
command = gunicorn api -w 4 -b 127.0.0.1:5000
startsecs = 5

这是非常基本的配置,它还有很多的配置项,比如控制日志、stdout/stderr重定向、重启策略等。这个工具真不错。

一旦你完成了配置,请确保Docker将其复制到了容器中。

ADD supervisord.conf /etc/supervisord.conf

让Supervisor作为容器的自启动命令。

CMD supervisord -c /etc/supervisord.conf

它将会在容器启动的时候,运行Gunicorn和Nginx。如果已经配置过了,那将会按需重启它们。
## 学到的东西以及未来的目标
我们已经花了很长时间在Docker中部署代码,并且接下来会投入更多的时间。在使用Docker的过程中,我们学到的最重要经验就是如何最小化思考(think minimally)。在一个容器中运行你的整个系统真的很诱人,但是在应用各自的容器中运行应用进程却更容易维护。一般情况下,我们会在容器中运行Nignx和Web服务器,并且在一些场景中,使用单独的容器来运行Nginx却没有任何优势,它可能只会增加复杂度。我们发现对于大多数情况,它在容器中的开销是可接受的。

我希望这些信息对各位有价值!当我们团队学到更多最佳实践时,我会更新这篇文章。

原文链接:Deploying Python with Docker(翻译:孙科 校对:李颖杰)

====================
译者介绍
学生一枚,撸代码站点维护者,专注于Linux系统编程和编译技术。热衷于学习各种语言和技术。目前正在开发一个Java Web框架(毕设)和一个web服务器(D语言编写)。

用Python/Keras/Flask/Docker在Kubernetes上部署深度学习模型

hokingyang 发表了文章 • 0 个评论 • 3443 次浏览 • 2018-10-29 10:15 • 来自相关话题

# 简单到老板也可以亲自部署 这篇博文演示了如何通过Docker和Kubernetes,用Keras部署深度学习模型,并且通过Flask提供REST API服务。 这个模型并不是强壮到可供生产的模型,而是给Kubernet ...查看全部
# 简单到老板也可以亲自部署
这篇博文演示了如何通过Docker和Kubernetes,用Keras部署深度学习模型,并且通过Flask提供REST API服务。

这个模型并不是强壮到可供生产的模型,而是给Kubernetes新手一个尝试的机会。我在Google Cloud上部署了这个模型,而且工作的很好。另外用户可以用同样的步骤重现以上功能。如果用户担心成本,Google提供了大量免费机会,这个演示基本没有花钱。
# 为什么用Kubernetes来做机器学习和数据科学
Kubernetes以及Cloud Native,正在席卷整个世界,我们已经感受到了。我们正处在一个由AI/Big Data/Cloud驱动的技术风暴中心,Kubernetes也正在加入这个中心。

但是如果从数据科学角度看并没有使用Kubernetes的特殊原因。但是从部署,扩展和管理REST API方面来看,Kubernetes正在实现简易化的特性。
# 步骤预览

  1. 在Google Cloud上创建用户
  2. 使用Keras/Flask/Docker搭建一个REST API的机器学习模型服务
  3. 用Kubernetes部署上述模型
  4. enjoy it

## 步骤一:在Google Cloud上创建用户
我在Google Compute Engine上创建了一个对外提供服务的容器化深度学习模型,当然Google平台并不是必须的,只要能够安装Docker,随便选择平台模式。
1.png

进入Google云平台,点击左侧屏幕选择Compute Engine,启动Google Cloud VM。然后选择“Create Instance”,可以看到已经运行的实例。
2.png

下一步选择计算资源。默认设置就足够,因为只是演示,我选择了4vCPUs和15G内存。
3.png

选择操作系统和磁盘大小。我选择了CentOS 7,100G硬盘。建议磁盘大于10G,因为每个Docker容器有1G大小。
4.png

最后一步是配置允许HTTP/S工作的防火墙策略。建议选择全部透明,以便减少麻烦。
5.png

选择“Create”,一切进展顺利。
6.png

## 步骤二:用Keras创建深度学习模型
SSH登录到虚机开始建立模型。最简单方式就是点击虚机下方的SSH图标,会在浏览器中打开一个终端。
7.png

1、删除预装Docker
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine


2、安装最新Docker版本
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager — add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce

3、启动容器运行测试脚本
sudo systemctl start docker
sudo docker run hello-world

以下是正确输出:
Hello from Docker!
This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal

4、创建深度学习模型
这里会借用Adrian Rosebrock的一个脚本,他提供了使用Keras的深度学习模型并通过Flask提供服务的教程,可以从这里访问。

这个模型可以直接执行。但是我修改了两个配置信息:

首先,改变了容器配置,默认flask使用127.0.0....作为默认服务地址,这会在容器内部运行时出现问题。我将它修改成0.0.0.0,这样就可以实现对外和对内都可以工作的IP地址。

第二是关于Tensorflow的配置,可以从GitHub中找到这个问题描述
global graph
graph = tf.get_default_graph()
...
with graph.as_default():
preds = model.predict(image)

运行脚本,首先创建专用目录:
mkdir keras-app
cd keras-app

创建app.py文件:
`vim app.py`
# USAGE
# Start the server:
# python app.py
# Submit a request via cURL:
# curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict'

# import the necessary packages
from keras.applications import ResNet50
from keras.preprocessing.image import img_to_array
from keras.applications import imagenet_utils
from PIL import Image
import numpy as np
import flask
import io
import tensorflow as tf

# initialize our Flask application and the Keras model
app = flask.Flask(__name__)
model = None

def load_model():
# load the pre-trained Keras model (here we are using a model
# pre-trained on ImageNet and provided by Keras, but you can
# substitute in your own networks just as easily)
global model
model = ResNet50(weights="imagenet")
global graph
graph = tf.get_default_graph()

def prepare_image(image, target):
# if the image mode is not RGB, convert it
if image.mode != "RGB":
image = image.convert("RGB")

# resize the input image and preprocess it
image = image.resize(target)
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
image = imagenet_utils.preprocess_input(image)

# return the processed image
return image

@app.route("/predict", methods=["POST"])
def predict():
# initialize the data dictionary that will be returned from the
# view
data = {"success": False}

# ensure an image was properly uploaded to our endpoint
if flask.request.method == "POST":
if flask.request.files.get("image"):
# read the image in PIL format
image = flask.request.files["image"].read()
image = Image.open(io.BytesIO(image))

# preprocess the image and prepare it for classification
image = prepare_image(image, target=(224, 224))

# classify the input image and then initialize the list
# of predictions to return to the client
with graph.as_default():
preds = model.predict(image)
results = imagenet_utils.decode_predictions(preds)
data["predictions"] = []

# loop over the results and add them to the list of
# returned predictions
for (imagenetID, label, prob) in results[0]:
r = {"label": label, "probability": float(prob)}
data["predictions"].append(r)

# indicate that the request was a success
data["success"] = True

# return the data dictionary as a JSON response
return flask.jsonify(data)

# if this is the main thread of execution first load the model and
# then start the server
if __name__ == "__main__":
print(("* Loading Keras model and Flask starting server..."
"please wait until server has fully started"))
load_model()
app.run(host='0.0.0.0')

5、创建requirements.txt文件

为了在容器内运行代码,需要创建requirements.txt文件,其中包括需要运行的包,例如keras、flask、一起其它相关包。这样无论在哪里运行代码,依赖包都保持一致。
keras
tensorflow
flask
gevent
pillow
requests

6、创建Dockerfile
FROM python:3.6
WORKDIR /app
COPY requirements.txt /app
RUN pip install -r ./requirements.txt
COPY app.py /app
CMD ["python", "app.py"]~

首先让容器自行下载Python 3安装image,然后让Python调用pip安装requirements.txt中的依赖包,最后运行python app.py。
7、创建容器
sudo docker build -t keras-app:latest .

在keras-app目录下创建容器,后台开始安装Python 3 image等在步骤6中定义的操作。

8、运行容器
sudo docker run -d -p 5000:5000 keras-app

用`sudo docker ps -a`检查容器状态,应该看到如下输出:
[gustafcavanaugh@instance-3 ~]$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d82f65802166 keras-app "python app.py" About an hour ago Up About an hour 0.0.0.0:5000->5000/tcp nervous_northcutt

9、测试模型

现在可以测试此模型。用狗的照片作为输入,可以返回狗的品种。在Adrian的示例中都有详细代码和图片,我们也使用它们,并保存自工作目录下,命名为dog.jpg。
1_FJWlc5VIb2k5Y64DgMZuww.jpeg

执行命令:
curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict'

应该得到如下输出:
 {"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true} 

可以看到此模型成功将狗归类为比格犬。下一步,我们用Kubernetes部署容器模型。
## 第三步:用Kubernetes部署模型
1、创建Docker Hub账号

第一步需要在Docker hub上传模型,以便使用Kubernetes集中管理。

2、登录到Docker Hub

`sudo docker login`, 登录到Docker Hub,应该看到如下输出:
Login Succeeded


3、给容器打标签

给模型容器命名,上传前先给它打标签。

`sudo docker images`,应该得到容器的id,输出如下:
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE keras-app           latest              ddb507b8a017        About an hour ago   1.61GB

打标签命令如下:
#Format
sudo docker tag /
#My Exact Command - Make Sure To Use Your Inputs
sudo docker tag ddb507b8a017 gcav66/keras-app

4、将模型容器上传到Docker Hub

运行命令如下:
#Format
sudo docker push /

#My exact command
sudo docker push gcav66/keras-app

5、创建Kubernetes集群

在Google Cloud Home界面,选择Kubernetes Engine。
8.png

创建新集群:
9.png

选择集群内节点资源,因为要启动三个节点(每个节点4vCPU和15G内存),至少需要12vCPU和45G内存。
10.png

连接集群,Google’s Kubernetes自动会在VM上安装Kubernetes。
11.png

在Kubernetes中运行容器:
kubectl run keras-app --image=gcav66/keras-app --port 5000

确认是否Pod正确运行`kubectl get pods`,输出如下:
gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get pods
NAME READY STATUS RESTARTS AGE
keras-app-79568b5f57-5qxqk 1/1 Running 0 1m

为了安全起见,将服务端口暴露与80端口:
kubectl expose deployment keras-app --type=LoadBalancer --port 80 --target-port 5000

确认服务正常启动:`kubectl get service`,正常输出如下:
gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
keras-app LoadBalancer 10.11.250.71 35.225.226.94 80:30271/TCP 4m
kubernetes ClusterIP 10.11.240.1 443/TCP 18m


提取cluster-IP,并将其合并于服务提交命令:`curl -X POST -F image=@dog.jpg 'http:///predict'`,得到正常输入如下:
$ curl -X POST -F image=@dog.jpg 'http://35.225.226.94/predict'
{"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true}

## 第四步:总结
本文提供了一个使用Keras和Flask提供REST API服务的深度学习模型,并把它集成到容器内部,上传到Docker Hub,并用Kubernetes部署,非常容易地实现了对外提供服务和访问。

现在,我们可以对这个项目进行很多改进。对于初学者,可以改变本地Python服务到更加强壮的gunicorn;可以横向扩展Kubernetes,实现服务扩容;也可以从头搭建一套Kubernetes环境。

原文链接:Deploy Your First Deep Learning Model On Kubernetes With Python, Keras, Flask, and Docker(翻译:杨峰)

如何在Python中使用ZeroMQ和Docker构建微服务架构

Leo_li 发表了文章 • 0 个评论 • 14693 次浏览 • 2016-02-28 18:43 • 来自相关话题

@Container容器技术大会将于6月4日在上海光大会展中心国际大酒店举办,来自携程、PPTV、蚂蚁金服、京东、浙江移动、海尔电器、唯品会、eBay、道富银行、麻袋理财等公司的技术负责人将带来实践经验分享,3月21日之前购票只需238元,欢迎感兴趣的同学抢购 ...查看全部
@Container容器技术大会将于6月4日在上海光大会展中心国际大酒店举办,来自携程、PPTV、蚂蚁金服、京东、浙江移动、海尔电器、唯品会、eBay、道富银行、麻袋理财等公司的技术负责人将带来实践经验分享,3月21日之前购票只需238元,欢迎感兴趣的同学抢购。
#微服务是什么?
微服务是一种架构风格,它包括多个彼此间进行通信的独立进程。在设计上,这些进程具有高度的可扩展性、相互解耦而且一次只完成一个较小的任务。这些服务都拥有自己的资源以及通过网络实现彼此间通信的进程。

相比于靠后端的单体结构来封装所有服务器逻辑的传统客户端-服务器架构(C/S架构)而言,微服务架构的差异性体现在关注点分离(Separation of concern)。这种设计模式更易于维护,使得灵活性、可扩展性及容错能力更强。但是这种分布式架构所的不足之处体现在如果设计不合理就会使得排错及维护变得复杂。
#一个简单微服务的例子
让我们来分析这样的一个场景:你正在使用微服务模式构建一个电子商务网店。

对于一个电商网店上的常见商品,好比说iPhone,其详情页会显示:

  • 产品的及基本信息
  • 你的购买历史
  • 哪些人买了iPhone也买了手机套
  • 与苹果手机相关的优惠和折扣
  • 店家的数据
  • 送货方式
  • 推荐商品等等
此外,这个简单的产品详情页的接口将有多个版本的来匹配Web、移动端以及用于第三方应用程序的REST API。在微服务模式中数据分布在多个服务之间。在这个例子中,服务包括:
  • 产品详情服务
  • 商家服务
  • 支付服务
  • 优惠及折扣服务
  • 库存服务
  • 定价服务
  • 回顾服务
  • 推荐服务
这些独立的服务是如何被访问的呢?解决办法是使用一个API网管,它作为所有客户端的单一入口并且根据需求调用分布在整个基础架构中的特定微服务。以上模式的行业应用案例是NetFlix API网关,它具有支持不同设备的多个API客户端。你可以点击此处了解更多。##构建一个简单的微服务目前有很多方法可以用于构建你的微服务。在本文中我们将使用ZeroMQ来创建两个进程之间的通信。ZeroMQ提供了用于在套接字之上开发可扩展、分布式systed的构建块。它使用椭圆曲线密码体制(第四版)来实现安全性,并提供了即刻开启的通讯模式。关于ZMQ,还有很多优点。MQ即是针对异步工作而设计的线程化消息队列。谈论太多zeroMQ的内容已经超出了本文的范畴,你可以阅读使用zeromq以及zeromq用于分布式系统。我们要使用的另一个工具是Docker。本文假设读者对Docker已经有了基础的了解。ZeroMQ有很多种通讯模式,为了开始我们的工作,让我们用ZeroMQ和Flask来配置一个简单的PUB-SUB。下图展示了组件之间的关系和数据流。
microservices-docker-python-apcelent.png
  • 1&3 - 一个flask服务器运行在5000端口上而且其URL是`/downcase/`。该URL用来接受(GET)请求,而所有格式为的请求将收到回应:答谢字符将会转换为小写字符并返回。
  • 2 - 回应的消息也被发送给同一个容器中的ZMQ发布者(Publisher)
  • 4,5 - ZMQ订阅者(subscriber)持续监听并将来自ZMQ服务器的消息保存到名为`subscriber.log`的文件中

##创建服务器
首先看一下我们的Dockerfile
FROM ubuntu:14.04 
RUN apt-get update
RUN apt-get install -y --force-yes python python-dev python-setuptools software-properties-common gcc python-pip
RUN apt-get clean all

RUN pip install pyzmq

RUN pip install Flask

ADD zmqserver.py /tmp/zmqserver.py


# # Flask Port

EXPOSE 5000


# # Zmq Sub Server

EXPOSE 4444

CMD ["python","/tmp/zmqserver.py"]

我们选择Ubuntu 14.04作为容器操作系统。我们安装了基本的软件包。通过pip,我们安装pyzmq(zeromq的Python绑定)同时也安装了Flask。接着我们导出端口5000(flask服务器)和4444(发布者运行的端口)。此外,我们复制了包含所有flask及zeromq pythond代码的脚本文件`zmqserver.py`并运行它。

现在我们来看一下zmqserver.py的内容:
# server.py 
import time
import zmq

HOST = '127.0.0.1'
PORT = '4444'

_context = zmq.Context()
_publisher = _context.socket(zmq.PUB)
url = 'tcp://{}:{}'.format(HOST, PORT)

def publish_message(message):
try:
_publisher.bind(url)
time.sleep(1)
_publisher.send(message)

except Exception as e:
print "error {}".format(e)
finally: _publisher.unbind(url)

from flask import Flask
from flask import request
app = Flask(__name__)

@app.route("/downcase/", methods=['GET'])
def lowerString():

_strn = request.args.get('param')
response = 'lower case of {} is {}'.format(_strn, _strn.lower()) publish_message(response)
return response

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False)

ZMQ发布者运行在4444端口上。我们创建了一个context并且声明了URL。我们运行了flask app,它通过URL `/downcase/`把GET获得的参数`Param`转换成小写字符,这就是服务的应答。应答的字符串是`published`,它作为一个消息把相同的字符串返回给浏览器。

为了构建以上的Docker映像(image),我们执行以下的命令:
`sudo docker build -t docker-zmq-pub`
并且在该映像之上执行:
`docker run --name docker-pub-server -p 5000:5000 -p 4444:4444 -t docker-zmq-pub`。

我们把容器中的端口5000和4444映射到这台主机上,于是无论客户端在哪里,它们都可以订阅这个发布者。
##订阅者客户端
# client.py
import zmq
import sys
import time
import logging
import os

HOST = '127.0.0.1'
PORT = '4444'

logging.basicConfig(filename='subscriber.log', level=logging.INFO)


class ZClient(object):

def __init__(self, host=HOST, port=PORT):
"""Initialize Worker"""
self.host = host
self.port = port
self._context = zmq.Context()
self._subscriber = self._context.socket(zmq.SUB)
print "Client Initiated"

def receive_message(self):
"""Start receiving messages"""
self._subscriber.connect('tcp://{}:{}'.format(self.host, self.port))
self._subscriber.setsockopt(zmq.SUBSCRIBE, b"")

while True:
print 'listening on tcp://{}:{}'.format(self.host, self.port)
message = self._subscriber.recv()
print message
logging.info(
'{} - {}'.format(message, time.strftime("%Y-%m-%d %H:%M")))

if __name__ == '__main__':
zs = ZClient()
zs.receive_message()

我们声明了发布者的IP地址及端口,当前它运行在同一个的主机上因此地址是127开头。我们在URL `tcp://IP:PORT`上进行监听。一旦我们收到一个消息,就将其附上时间戳后记录到名为`subscriber.log`的文件中。运行客户端要做的所有工作就是执行 `python .py`。如果你在以上的架构上进行构建,它可以很好地充当近实时的日志聚合引擎。

我在Unbuntu主机上对以上的代码进行了测试。这里所用的代码保管在GitHub上。这是一个如何配置ZMQ、Docker和Python服务器的基础讲解,在我的下一片文章中我们会使用我们已经学习的东西构建简单的微服务。

希望这篇文章对你有所帮助。

原文链接:How to Setup Microservices Architecture in Python with ZeroMQ & Docker(翻译:李毅)

使用Docker部署Python应用的一些最佳实践

codesun 发表了文章 • 0 个评论 • 22630 次浏览 • 2015-02-06 00:23 • 来自相关话题

【编者的话】本篇文章源自作者团队在长期开发过程中总结的宝贵经验,其中Supervisor、Gunicorn以及Nginx更是在使用Python开发Web应用时最常用的软件,因此对于打算使用Docker部署Python应用的读者而言,这些最佳实践是很有参考价值。 ...查看全部
【编者的话】本篇文章源自作者团队在长期开发过程中总结的宝贵经验,其中Supervisor、Gunicorn以及Nginx更是在使用Python开发Web应用时最常用的软件,因此对于打算使用Docker部署Python应用的读者而言,这些最佳实践是很有参考价值。同时希望各位在日常实践过程中,也能将各自踩到过的“坑”以及宝贵的经验分享出来,大家共同进步!


我们可以使用Docker简单而高效的部署Python应用,同时,也有一些最佳实践来帮助我们愉快的完成部署。当然,也不是说这些最佳实践就是完成部署的唯一方式,只不过我们团队发现它们具有高可用性,并且容易维护。注意本文中大多数内容都只是代表我的立场,基于Docker的实现方式有很多,你可以随便选择。本文中我不会过多的介绍Volume,因为它可能需要一个单独的话题来解释。我们通常会使用Volume将源代码复制到容器中,而不是在每次运行时都重新构建。
## DEBIAN_FRONTEND
Docker用户应该都很熟悉这个环境变量,它告知操作系统应该从哪儿获得用户输入。如果设置为"noninteractive",你就可以直接运行命令,而无需向用户请求输入(译者注:所有操作都是非交互式的)。这在运行apt-get命令的时候格外有用,因为它会不停的提示用户进行到了哪步并且需要不断确认。非交互模式会选择默认的选项并以最快的速度完成构建。

请确保你只在Dockerfile中调用的RUN命令中设置了该选项,而不是使用ENV命令进行全局的设置,因为ENV命令在整个容器运行过程中都会生效,所以当你通过BASH和容器进行交互时,如果进行了全局设置那就会出问题。例子如下:

# 正确的做法 - 只为这个命令设置ENV变量
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python3
# 错误地做法 - 为接下来的任何命令都设置ENV变量,包括正在运行地容器
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get install -y python3

## requirements.txt
相比于基本的代码(codebase),应用的依赖很少发生变化,因此我们可以在Dockerfile中安装项目依赖,这也可以加快之后的构建速度(之后的构建只需要构建变更的代码)。Docker容器的层级构建可以缓存依赖安装的过程,所以之后的的构建速度会非常快,因为它不需要重新下载和构建依赖。
## 文件顺序
按照上面的思路(利用缓存)来推断,文件添加到容器的顺序至关重要。我们应该把频繁变更的文件放置到Dockerfile的下方,以便充分使用缓存来加速Docker的构建过程。例如,应用配置、系统配置和依赖都很少改变,我们就可以把它们放到Dockerfile的顶部。而源文件,比如路由文件、视图(views)和数据库代码会经常发生改变,所以我们就可以把它们放在Dockerfile的下方,注意是Docker配置命令(EXPOSE、ENV等)的下方。

另外,不要考虑如何将你的文件拷贝到Docker,它不会加快你的构建速度,因为大多数的文件根本不会用到,比如应用源文件。
## 应用密钥
之前我们一直不知道如何把应用密钥安全的传递给应用,后来我们发现可以使用`docker run`命令中的`env-file`参数。我们会把所有的密钥和配置都放在app_config.list文件中,然后通过这个文件交付给应用。具体如下:

docker run -d -t -—env-file app_config.list

这个方法允许我们简单地改变应用设置和密钥,而无需重建一个容器。

注意:请务必确保app_config.list在.gitignore文件的记录中,不然它不会被检录到源文件中。
## Gunicorn
我们使用Gunicorn作为容器内部的应用服务器,Gunicorn非常的稳定并且性能很好,它有非常多的配置选项,比如指定worker数量和类型(green threads、gevent等)的能力,你可以根据负载来调整应用,以获得最佳性能。

启动Gunicorn很简单:

# 安装
pip3 install gunicorn

# 运行服务器
gunicorn api:app -w 4 -b 127.0.0.1:5000

最后是在Nginx后面运行你的应用服务器,这样你可以进行负载均衡。
## supervisord
你是不是想过在容器中运行多个进程?我想Supervisord绝对是你的最佳辅助工具。假设我们想部署这样一个容器,它包含Nginx反向代理以及Gunicorn应用。你通过BASH脚本可能就能实现,但是让我们想更加简洁一点。

Supevisor是“一个进程控制系统,它支持用户在类UNIX操作系统上,监视并控制一些进程”。听起来很完美!你需要先在你的Docker容器内安装Supervisor。

RUN DEBIAN_FRONTEND=noninteractive apt-get install -y
supervisor

为了让Supervisor知道该运行什么以及如何管理进程,我们接下来需要为它写一个配置文件。

[supervisord]
nodaemon = true # 这个会让supervisord运行在前端

[program:nginx] # 你想运行的第一个程序的命名
command = /usr/sbin/nginx # nginx可执行程序的路径
startsecs = 5 # 如果nginx保持开启5s,我们视为启动成功

[program:app-gunicorn]
command = gunicorn api -w 4 -b 127.0.0.1:5000
startsecs = 5

这是非常基本的配置,它还有很多的配置项,比如控制日志、stdout/stderr重定向、重启策略等。这个工具真不错。

一旦你完成了配置,请确保Docker将其复制到了容器中。

ADD supervisord.conf /etc/supervisord.conf

让Supervisor作为容器的自启动命令。

CMD supervisord -c /etc/supervisord.conf

它将会在容器启动的时候,运行Gunicorn和Nginx。如果已经配置过了,那将会按需重启它们。
## 学到的东西以及未来的目标
我们已经花了很长时间在Docker中部署代码,并且接下来会投入更多的时间。在使用Docker的过程中,我们学到的最重要经验就是如何最小化思考(think minimally)。在一个容器中运行你的整个系统真的很诱人,但是在应用各自的容器中运行应用进程却更容易维护。一般情况下,我们会在容器中运行Nignx和Web服务器,并且在一些场景中,使用单独的容器来运行Nginx却没有任何优势,它可能只会增加复杂度。我们发现对于大多数情况,它在容器中的开销是可接受的。

我希望这些信息对各位有价值!当我们团队学到更多最佳实践时,我会更新这篇文章。

原文链接:Deploying Python with Docker(翻译:孙科 校对:李颖杰)

====================
译者介绍
学生一枚,撸代码站点维护者,专注于Linux系统编程和编译技术。热衷于学习各种语言和技术。目前正在开发一个Java Web框架(毕设)和一个web服务器(D语言编写)。

用户直接运行代码库源文件( Python 单体应用)会有什么弊端?

回复

mowangmm 发起了问题 • 1 人关注 • 0 个回复 • 1376 次浏览 • 2018-03-14 17:04 • 来自相关话题

有没有image 包含各种数据库的驱动

回复

productive 发起了问题 • 1 人关注 • 0 个回复 • 1453 次浏览 • 2017-04-17 10:56 • 来自相关话题

用Python/Keras/Flask/Docker在Kubernetes上部署深度学习模型

hokingyang 发表了文章 • 0 个评论 • 3443 次浏览 • 2018-10-29 10:15 • 来自相关话题

# 简单到老板也可以亲自部署 这篇博文演示了如何通过Docker和Kubernetes,用Keras部署深度学习模型,并且通过Flask提供REST API服务。 这个模型并不是强壮到可供生产的模型,而是给Kubernet ...查看全部
# 简单到老板也可以亲自部署
这篇博文演示了如何通过Docker和Kubernetes,用Keras部署深度学习模型,并且通过Flask提供REST API服务。

这个模型并不是强壮到可供生产的模型,而是给Kubernetes新手一个尝试的机会。我在Google Cloud上部署了这个模型,而且工作的很好。另外用户可以用同样的步骤重现以上功能。如果用户担心成本,Google提供了大量免费机会,这个演示基本没有花钱。
# 为什么用Kubernetes来做机器学习和数据科学
Kubernetes以及Cloud Native,正在席卷整个世界,我们已经感受到了。我们正处在一个由AI/Big Data/Cloud驱动的技术风暴中心,Kubernetes也正在加入这个中心。

但是如果从数据科学角度看并没有使用Kubernetes的特殊原因。但是从部署,扩展和管理REST API方面来看,Kubernetes正在实现简易化的特性。
# 步骤预览

  1. 在Google Cloud上创建用户
  2. 使用Keras/Flask/Docker搭建一个REST API的机器学习模型服务
  3. 用Kubernetes部署上述模型
  4. enjoy it

## 步骤一:在Google Cloud上创建用户
我在Google Compute Engine上创建了一个对外提供服务的容器化深度学习模型,当然Google平台并不是必须的,只要能够安装Docker,随便选择平台模式。
1.png

进入Google云平台,点击左侧屏幕选择Compute Engine,启动Google Cloud VM。然后选择“Create Instance”,可以看到已经运行的实例。
2.png

下一步选择计算资源。默认设置就足够,因为只是演示,我选择了4vCPUs和15G内存。
3.png

选择操作系统和磁盘大小。我选择了CentOS 7,100G硬盘。建议磁盘大于10G,因为每个Docker容器有1G大小。
4.png

最后一步是配置允许HTTP/S工作的防火墙策略。建议选择全部透明,以便减少麻烦。
5.png

选择“Create”,一切进展顺利。
6.png

## 步骤二:用Keras创建深度学习模型
SSH登录到虚机开始建立模型。最简单方式就是点击虚机下方的SSH图标,会在浏览器中打开一个终端。
7.png

1、删除预装Docker
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine


2、安装最新Docker版本
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager — add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce

3、启动容器运行测试脚本
sudo systemctl start docker
sudo docker run hello-world

以下是正确输出:
Hello from Docker!
This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal

4、创建深度学习模型
这里会借用Adrian Rosebrock的一个脚本,他提供了使用Keras的深度学习模型并通过Flask提供服务的教程,可以从这里访问。

这个模型可以直接执行。但是我修改了两个配置信息:

首先,改变了容器配置,默认flask使用127.0.0....作为默认服务地址,这会在容器内部运行时出现问题。我将它修改成0.0.0.0,这样就可以实现对外和对内都可以工作的IP地址。

第二是关于Tensorflow的配置,可以从GitHub中找到这个问题描述
global graph
graph = tf.get_default_graph()
...
with graph.as_default():
preds = model.predict(image)

运行脚本,首先创建专用目录:
mkdir keras-app
cd keras-app

创建app.py文件:
`vim app.py`
# USAGE
# Start the server:
# python app.py
# Submit a request via cURL:
# curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict'

# import the necessary packages
from keras.applications import ResNet50
from keras.preprocessing.image import img_to_array
from keras.applications import imagenet_utils
from PIL import Image
import numpy as np
import flask
import io
import tensorflow as tf

# initialize our Flask application and the Keras model
app = flask.Flask(__name__)
model = None

def load_model():
# load the pre-trained Keras model (here we are using a model
# pre-trained on ImageNet and provided by Keras, but you can
# substitute in your own networks just as easily)
global model
model = ResNet50(weights="imagenet")
global graph
graph = tf.get_default_graph()

def prepare_image(image, target):
# if the image mode is not RGB, convert it
if image.mode != "RGB":
image = image.convert("RGB")

# resize the input image and preprocess it
image = image.resize(target)
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
image = imagenet_utils.preprocess_input(image)

# return the processed image
return image

@app.route("/predict", methods=["POST"])
def predict():
# initialize the data dictionary that will be returned from the
# view
data = {"success": False}

# ensure an image was properly uploaded to our endpoint
if flask.request.method == "POST":
if flask.request.files.get("image"):
# read the image in PIL format
image = flask.request.files["image"].read()
image = Image.open(io.BytesIO(image))

# preprocess the image and prepare it for classification
image = prepare_image(image, target=(224, 224))

# classify the input image and then initialize the list
# of predictions to return to the client
with graph.as_default():
preds = model.predict(image)
results = imagenet_utils.decode_predictions(preds)
data["predictions"] = []

# loop over the results and add them to the list of
# returned predictions
for (imagenetID, label, prob) in results[0]:
r = {"label": label, "probability": float(prob)}
data["predictions"].append(r)

# indicate that the request was a success
data["success"] = True

# return the data dictionary as a JSON response
return flask.jsonify(data)

# if this is the main thread of execution first load the model and
# then start the server
if __name__ == "__main__":
print(("* Loading Keras model and Flask starting server..."
"please wait until server has fully started"))
load_model()
app.run(host='0.0.0.0')

5、创建requirements.txt文件

为了在容器内运行代码,需要创建requirements.txt文件,其中包括需要运行的包,例如keras、flask、一起其它相关包。这样无论在哪里运行代码,依赖包都保持一致。
keras
tensorflow
flask
gevent
pillow
requests

6、创建Dockerfile
FROM python:3.6
WORKDIR /app
COPY requirements.txt /app
RUN pip install -r ./requirements.txt
COPY app.py /app
CMD ["python", "app.py"]~

首先让容器自行下载Python 3安装image,然后让Python调用pip安装requirements.txt中的依赖包,最后运行python app.py。
7、创建容器
sudo docker build -t keras-app:latest .

在keras-app目录下创建容器,后台开始安装Python 3 image等在步骤6中定义的操作。

8、运行容器
sudo docker run -d -p 5000:5000 keras-app

用`sudo docker ps -a`检查容器状态,应该看到如下输出:
[gustafcavanaugh@instance-3 ~]$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d82f65802166 keras-app "python app.py" About an hour ago Up About an hour 0.0.0.0:5000->5000/tcp nervous_northcutt

9、测试模型

现在可以测试此模型。用狗的照片作为输入,可以返回狗的品种。在Adrian的示例中都有详细代码和图片,我们也使用它们,并保存自工作目录下,命名为dog.jpg。
1_FJWlc5VIb2k5Y64DgMZuww.jpeg

执行命令:
curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict'

应该得到如下输出:
 {"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true} 

可以看到此模型成功将狗归类为比格犬。下一步,我们用Kubernetes部署容器模型。
## 第三步:用Kubernetes部署模型
1、创建Docker Hub账号

第一步需要在Docker hub上传模型,以便使用Kubernetes集中管理。

2、登录到Docker Hub

`sudo docker login`, 登录到Docker Hub,应该看到如下输出:
Login Succeeded


3、给容器打标签

给模型容器命名,上传前先给它打标签。

`sudo docker images`,应该得到容器的id,输出如下:
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE keras-app           latest              ddb507b8a017        About an hour ago   1.61GB

打标签命令如下:
#Format
sudo docker tag /
#My Exact Command - Make Sure To Use Your Inputs
sudo docker tag ddb507b8a017 gcav66/keras-app

4、将模型容器上传到Docker Hub

运行命令如下:
#Format
sudo docker push /

#My exact command
sudo docker push gcav66/keras-app

5、创建Kubernetes集群

在Google Cloud Home界面,选择Kubernetes Engine。
8.png

创建新集群:
9.png

选择集群内节点资源,因为要启动三个节点(每个节点4vCPU和15G内存),至少需要12vCPU和45G内存。
10.png

连接集群,Google’s Kubernetes自动会在VM上安装Kubernetes。
11.png

在Kubernetes中运行容器:
kubectl run keras-app --image=gcav66/keras-app --port 5000

确认是否Pod正确运行`kubectl get pods`,输出如下:
gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get pods
NAME READY STATUS RESTARTS AGE
keras-app-79568b5f57-5qxqk 1/1 Running 0 1m

为了安全起见,将服务端口暴露与80端口:
kubectl expose deployment keras-app --type=LoadBalancer --port 80 --target-port 5000

确认服务正常启动:`kubectl get service`,正常输出如下:
gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
keras-app LoadBalancer 10.11.250.71 35.225.226.94 80:30271/TCP 4m
kubernetes ClusterIP 10.11.240.1 443/TCP 18m


提取cluster-IP,并将其合并于服务提交命令:`curl -X POST -F image=@dog.jpg 'http:///predict'`,得到正常输入如下:
$ curl -X POST -F image=@dog.jpg 'http://35.225.226.94/predict'
{"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true}

## 第四步:总结
本文提供了一个使用Keras和Flask提供REST API服务的深度学习模型,并把它集成到容器内部,上传到Docker Hub,并用Kubernetes部署,非常容易地实现了对外提供服务和访问。

现在,我们可以对这个项目进行很多改进。对于初学者,可以改变本地Python服务到更加强壮的gunicorn;可以横向扩展Kubernetes,实现服务扩容;也可以从头搭建一套Kubernetes环境。

原文链接:Deploy Your First Deep Learning Model On Kubernetes With Python, Keras, Flask, and Docker(翻译:杨峰)

技术漫谈 | 使用docker-compose进行python开发

wise2c 发表了文章 • 0 个评论 • 4047 次浏览 • 2017-07-11 17:36 • 来自相关话题

作者:郑云龙 Docker提供了容器级别的资源隔离。由于Python的外部依赖管理中存在的问题,我们通常会使用virtualenv来对不同的项目创建其唯一的依赖环境。这时利用Docker进行Python开发,可以轻松解决不同Pytho ...查看全部
作者:郑云龙

Docker提供了容器级别的资源隔离。由于Python的外部依赖管理中存在的问题,我们通常会使用virtualenv来对不同的项目创建其唯一的依赖环境。这时利用Docker进行Python开发,可以轻松解决不同Python项目之间的依赖隔离问题。

作为应用程序,我们通常需要依赖于多种外部服务,比如数据库、缓存服务等等。Docker-compose就是在Docker容器的基础上,提供了统一的容器编排语言,可以让你更轻松的利用Docker构建你的应用环境。

编写Dockerfile

我们使用requirements.txt定义我们的第三方python包依赖

1微信截图_20170710112612.png


Project-Root
|– static
|– templates
|– server.py
|– requirements.txt
|– Dockerfile
|– docker-compose.yml

编写Dockerfile内容如下:

2微信截图_20170710113234.png


在Dockerfile中,我们主要目的:通过requirements.txt文件安装第三方的Python库依赖;利用Docker的容器隔离,可以忽略掉很多在本地开发中需要使用的东西,比如virtualenv。

编排我们的Docker容器

在案例中,应用程序依赖了mongodb作为数据存储服务,以及redis作为缓存服务。在一般情况下,作为开发团队要么我们搭建统一的mongodb;要不就每个人在开发机上单独部署。

而在Docker中,我们则不在需要做这么多无用的事情。 Docker官方提供了大量的基础容器,基本涵盖了日常开发中我们需要的大部分依赖。 在https://hub.docker.com/我们可以搜索到我们需要的基础镜像。

比如mongodb以及redis,在docker-hub上官方都提供了容器话的服务。

以redis容器为例,我们在本地搭建redis服务要做的事情主要包括两步:


3微信截图_20170710144513.png



这个时候我们就可以通过访问0.0.0.0:63775来访问我们的redis服务器了。

我们也可以通过Docker原生的命令来连接我们的应用容器和redis容器,以使我们的代码能够正常的访问redis服务

4微信截图_20170710144857.png


而事实上,我们可以使用更加简化的方式来定义我们的容器组合管理,使用Docker-compose(前身Fig)来定义我们的容器组合关系。

5微信截图_20170710145026.png


这里我们定义了3个容器web、redis、mongo。 其中,web容器是通过当前目录的Dockerfile进行构建,同时将当前目录挂在到/app目录。 而redis和mongo则直接使用官方进行。

通过使用links,我们可以在web容器中通过 ‘redis:6375’以及’mongo:21707’直接访问相应的服务。

开始Coding吧

6微信截图_20170710145425.png


Docker会根据当前的目录下得Dockerfile构建基础镜像,并且使用python server.py运行程序,并且运行redis以及mongo服务。

同时由于使用了volumes挂载了本地目录到/app,此时如果我们是开启的Debug模式,我们就可以直接在本地使用你喜欢的文本编辑器去编写代码,并且更新的代码能够实时被重新加载。

当然在使用Docker中最漫长的过程就是,下镜像,下镜像&下镜像。

文末福利:请大家关注'Wise2C'公众号并回复【进群】,睿云小助手会第一时间拉你进入
【Docker企业落地实践群】,我们分享的各个企业案例项目的技术专家与用户代表,正在敬候您的光临,期待大家就项目的更多细节与疑问与群里的大牛们进行咨询探讨。
若需要了解更多有关Wise系列PaaS产品的详情,请与我们的市场团队联系: contact@wise2c.com

睿云智合微信截图_20170926111623.png


面面观 | 使用python 连接数据库,插入并查询数据--link

enncloud 发表了文章 • 0 个评论 • 1292 次浏览 • 2017-06-02 16:01 • 来自相关话题

1,将两个docker 连接起来 首先需要搭建环境: 在alpine下面创建mariadb数据库: http://blog.csdn.net/freewebsys/article/details/53540 ...查看全部
1,将两个docker 连接起来

首先需要搭建环境:
在alpine下面创建mariadb数据库:
http://blog.csdn.net/freewebsys/article/details/53540615
用户名密码是root。
然后创建http的Python环境:
http://blog.csdn.net/freewebsys/article/details/53509676
接下来做一个简单数据查询和插入操作。

2,python代码:
main.py
#!/usr/bin/python# -- coding: utf-8 --from flask import Flaskimport MySQLdb app = Flask(__name__) mysql_host = "mysql"mysql_user = "root"mysql_pwd = "root"mysql_db_name = "demo"""" CREATE DATABASE `demo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; CREATE TABLE `demo`.`user_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; """def query_db(sql): db = MySQLdb.connect(mysql_host, mysql_user, mysql_pwd, mysql_db_name) cur = db.cursor() try: cur.execute(sql) result = cur.fetchall() except: print("error: sql:" + sql) cur.close() db.close() return resultdef update_db(sql): print(sql) db = MySQLdb.connect(mysql_host, mysql_user, mysql_pwd, mysql_db_name) cur = db.cursor() try: cur.execute(sql) except: print("error: sql:" + sql) db.commit() cur.close() db.close()@app.route("/list")def list(): results = query_db(" select id,name from `demo`.`user_info` ") out = "results:\n" for result in results: id = result[0] name = result[1] out += "id:" + str(id) + ",name:" + name +"\n" return out@app.route("/add")def add(): sql = " insert ignore into `demo`.`user_info`(`name` ) values ('zhangsan') " update_db(sql) return "ok"if __name__ == "__main__": app.run(host='0.0.0.0', port=5000)


代码和之前的http没有太大区别,只是增加了数据库的查询和插入操作。
一共就有两个url,一个list,查询全部数据,一个add,写死增加。

3,创建数据库表
MySQL需要创建下数据库和表:

CREATE DATABASE `demo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;CREATE TABLE `demo`.`user_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`name` varchar(200) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这个user_info表一共就有两个字段,一个id自增,一个是name字符串的。
当然这个数据库不在本地,是另外的一个Docker 容器。
http在一个容器上面。

4,使用link连接起来
数据创建名称叫mariadb。
跑http。

docker run -d -p 5000:5000 --name py-http --link mariadb:mysql demo/py-http:1.0

特别注意这里的–link 容器名:昵称,然后对于py-http容器来说mysql就是昵称了。
可以直接看下evn环境:

# docker exec -it py-http
bashbash-4.3# env
HOSTNAME=db7f7aba7c2f
MYSQL_ENV_MYSQL_ROOT_PASSWORD=root
MYSQL_ENV_MARIADB_VERSION=10.1.19+maria-1~jessie
MYSQL_ENV_GOSU_VERSION=1.7
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_ENV_MARIADB_MAJOR=10.1
MYSQL_PORT_3306_TCP=tcp://172.17.0.2:3306
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
TZ=Asia/Shanghai
SHLVL=1HOME=/root
MYSQL_NAME=/py-http/mysql
MYSQL_PORT_3306_TCP_PROTO=tcp
MYSQL_PORT_3306_TCP_ADDR=172.17.0.2
MYSQL_PORT=tcp://172.17.0.2:3306
_=/usr/bin/env

可以看到,在py-http容器下面已经把mariadb容器的环境变量直接引入了。
并且查看hosts:
# cat /etc/hosts
127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 mysql 48bd5fbf3ddc mariadb
172.17.0.3 db7f7aba7c2f

可以看到有了mysql变量的host了。
在外部访问:就说明测试成功。数据库能插入查询了。

# curl http://127.0.0.1:5000/add
ok[root@localhost http]# curl http://127.0.0.1:5000/list
results:
id:1,name:zhangsan
id:2,name:zhangsan

4,总结

docker设计的挺好的,每一个容器虽然独立,但是还可以连接起来。
这样组成一个微服务的集群。通过环境变量,把代码和环境分离。
保证测试环境,开发环境,线上环境的的一致。
极大的方便了开发运维,link 命令也非常好使。
新智云官网www.enncloud.cn

Docker环境下运行Python + Selenium + Chrome

herryliq 发表了文章 • 0 个评论 • 9726 次浏览 • 2017-04-07 23:40 • 来自相关话题

Docker运行时占用的资源非常少,而且能将环境进行有效的隔离,可以快速的进行部署,因此可以将Docker与Selenium结合实现在容器中执行无界面的自动化操作。例如:自动测试、自动下载邮件等。 【深圳站|3天烧脑式Kubernet ...查看全部
Docker运行时占用的资源非常少,而且能将环境进行有效的隔离,可以快速的进行部署,因此可以将Docker与Selenium结合实现在容器中执行无界面的自动化操作。例如:自动测试、自动下载邮件等。

【深圳站|3天烧脑式Kubernetes训练营】培训内容包括:Kubernetes概述和架构、部署和核心机制分析、进阶篇——Kubernetes调工作原理及源码分析等。
#需求说明
通过Selenium自动的登录邮箱,下载邮箱中符合条件的邮件,并对邮件的内容进行解析存档。
#方案选择
Selenium官方提供了基于selenium hub的方式来管理Selenium的node节点,提供了分布式的远程调度方案,可以为SeleniumGrid添加各种类型的WebDriver。
selenium-hub.jpg

基于Selenium Grid的方案适用于以下场景:

  1. 通过Selenium自动访问网页时,需要阻塞等待与用户的交互,例如需要输入短信验证码的场景,通过Selenium Grid的调度,可以最优的利用所有可访问的资源,提高系统的并发执行效率。
  2. 需要对网页的兼容性进行测试时,可以接入多种内核的WebDriver到Selenium Grid中,这样在进行网页的自动化测试时,也同时对浏览器的兼容性进行了测试。
  3. 访问只支持IE内核的网站时,例如一些开发比较早的政府网站等。
  4. 访问需要安装安全控件的网站,例如网银的登录等。

本次的需求对时效性要求不高,邮件的获取需要由定时器进行触发,如果使用Selenium Grid,可能会增加了整个系统的复杂程度,分布式系统中每引入一个中间件,就会增加系统的复杂性,系统的可用性就会随之降低。

基于以上分析,考虑采用selenium handless的模式,在Docker中运行Chrome的WebDriver,完成最终邮件的自动获取。

在确定方案之后,需要寻找合适的Docker镜像,最终找到了chromium-xvfb这个镜像文件,集成了Chromium和Xvfb,可以满足在Docker容器中通过Selenium来进行无界面的操作。
#方案验证
1.下载chromium-xvfb的镜像文件
[root@prod ~]# docker pull markadams/chromium-xvfb-py2

2.交互模式运行
[root@prod ~]# docker run --rm -it markadams/chromium-xvfb-py2 bash

3.进入python环境
root@8016c66877aa:/usr/src/app# python

4.输入以下python代码
from selenium import webdriver
driver = webdriver.Chrome()
url = 'https://github.com/mark-adams/docker-chromium-xvfb/blob/master/samples/
python3/test_google.py'
driver.get(url)

没有错误输出,证明Chrome已经在Docker下可以正常工作。
#方案实施
以markadams/chromium-xvfb-py2作为基础镜像,重新编写的Dockerfile如下:
FROM markadams/chromium-xvfb-py2
WORKDIR /opt/zs5s/download-mail
COPY ./download-mail/.pip /root/.pip/
COPY ./download-mail/requirements.txt /opt/zs5s/download-mail/requirements.txt

RUN pip install --upgrade pip

RUN pip install -r requirements.txt && mkdir /tmp/downloaded_files

ENV DISPLAY :1
COPY ./download-mail /opt/zs5s/download-mail
COPY ./data_service /opt/zs5s/data_service
COPY ./save_mail /opt/zs5s/save_mail
COPY ./common/ /opt/zs5s/common
USER root

在编译运行后,发现在这个Docker容器内单纯的去访问网页没有问题,如果去执行带有键盘的操作时,会报错,错误信息如下:
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: unknown error: an X display is required for keycode conversions, consider using Xvfb
(Session info: chrome=57.0.2987.98)
(Driver info: chromedriver=2.28.455506 (18f6627e265f442aeec9b6661a49fe819aeeea1f),platform=Linux 4.4.27-moby x86_64)

经过调查是需要在启动容器时执行Xvfb,申请一块Screen,具体可以参考链接1。在Dockerfile中加入启动脚本:
CMD sh start.sh
start.sh的内容如下:

#!/bin/sh
export DISPLAY=:1
Xvfb $DISPLAY -ac -screen 0 1280x1024x8 &
sleep 1
ps -aux
tail -f start.sh #测试用,为了阻塞住容器内的进程

#结果验证
在新生成的容器内执行以下的Python脚本,可以正常执行,说明在Chrome中已经可以接收通过Selenium传入的键盘事件。
from splinter import Browser
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
url = 'https://github.com/mark-adams/docker-chromium-xvfb/blob/master/samples/python3/test_google.py'
browser = Browser('chrome')
browser.visit(url)
element = browser.find_by_xpath('/html/body/div[1]/header/div/div/div/div/form/label/input[1]')
element.type('aaaa')

#残留问题
在Python退出Selenium时,虽然执行了driver.quit()函数,但是实际上Chrome并没有真正的退出,似乎是Selenium的一个Bug,在3.1中提到了修正,但是好像没有起作用。

解决办法可以考虑执行shell脚本主动的kill掉Chrome的进程。
#参考链接

  1. protractor-sendkeys-not-working-an-x-display-is-required-for-keycode-conversion
  2. crawling-python-selenium-docker
  3. docker-chromium-xvfb

使用Rancher-Gen动态更新配置文件

Rancher 发表了文章 • 1 个评论 • 2627 次浏览 • 2017-01-25 09:44 • 来自相关话题

Docker和Rancher让大家能更轻易地部署和管理基于微服务的应用程序。然而,如果有些服务是依赖于其他动态服务的,那该如何管理它们的配置?你是否时常觉得,要是有方法能自动检测后端服务的变化,并实现配置文件的动态更新就好了?本文给你答案。 ...查看全部
Docker和Rancher让大家能更轻易地部署和管理基于微服务的应用程序。然而,如果有些服务是依赖于其他动态服务的,那该如何管理它们的配置?你是否时常觉得,要是有方法能自动检测后端服务的变化,并实现配置文件的动态更新就好了?本文给你答案。

前言

Docker和Rancher让大家能更轻易地部署和管理基于微服务的应用程序。然而,有一个关键的挑战是,如果有些服务是依赖于其他动态服务的,那该如何管理它们的配置?

试想以下情形:您有多个运行Web应用程序的后端容器,和一些将所有的请求都代理到这些后端容器的nginx容器。现在,你必须要部署一个新版本的Web应用程序,这意味着你需要构建和部署新版本的后端容器。在这些部署工作完成之后,nginx的配置需要更改为指向新的后端容器。那么,你该怎样处理nginx呢?改变其配置,构建一个新的容器并部署它?此刻的你是不是会觉得,要是有方法能自动检测后端服务的变化,并实现nginx的动态更新就好了?

这就是Rancher-Gen要闪亮登场的时刻!

Rancher-Gen是一个Python的工具,它能监听的Rancher服务的变化,并呈现一个用户指定的Jinja2模板。这允许用户为现有的、并基于这些变化的服务生成配置文件。另外,Rancher-Gen提供了一种机制,在模板呈现之后会运行通知命令。下面的教程会介绍如何自动生成运行ghost博客平台后端服务的nginx配置文件 。

教程

下面介绍的所有配置文件都可以在Rancher-Gen库中的演示目录下找到。

第1步 – 部署Ghost服务

为简单起见,我们打算使用Docker hub中的官方ghost镜像。因此,创建一个docker-compose.yml 文件,并添加以下的ghost服务:


ghost:
image: ghost
expose:
- "2368"


现在,用Rancher Compose部署ghost服务:


$ rancher-compose -p demo up -d ghost


第2步 – 用Rancher-Gen创建nginx镜像

下面是用以搭建nginx镜像的Dockerfile:


FROM phusion/baseimage:0.9.17
MAINTAINER pitrho

# Step 1 - Install nginx and python
ENV DEBIAN_FRONTEND noninteractive
RUN \
apt-add-repository -y ppa:nginx/stable && \
apt-get update && \
apt-get install -y python-software-properties \
wget \
nginx \
python-dev \
python-pip \
libev4 \
libev-dev \
expect-dev && \
rm -rf /var/lib/apt/lists/* && \
chown -R www-data:www-data /var/lib/nginx && \
apt-get clean

# Step 2 - Install rancher-gen
ENV RANCHER_GEN_VERSION 0.1.2
RUN pip install rancher-gen==$RANCHER_GEN_VERSION

# Step 3 - Define services
RUN mkdir /etc/service/nginx /etc/service/rancher_gen /nginxconf
COPY nginx_run /etc/service/nginx/run
COPY rancher-gen_run /etc/service/rancher_gen/run
COPY default.j2 /nginxconf

# Step 4 - Use baseimage-docker's init system.
CMD ["/sbin/my_init"]

# Step 5 - Expose ports.
EXPOSE 80
EXPOSE 443


让我们来一步一步拆解Dockerfile。步骤1和2不言自明:只需安装nginx、Python和Rancher-Gen。

第3步要设置镜像启动时运行的服务。第一个服务是nginx,它用/etc/servce/nginx文件运行。该文件的内容是:


#!/bin/bash
rancher-gen --host $RANCHER_GEN_HOST \
--port $RANCHER_GEN_PORT \
--access-key $RANCHER_GEN_ACCESS_KEY \
--secret-key $RANCHER_GEN_SECRET_KEY \
--project-id $RANCHER_GEN_PROJECT_ID \
$RANCHER_GEN_OPTIONS \
--notify "service nginx reload" /nginxconf/default.j2 /etc/nginx/sites-available/default


注意一下在通知步骤之后,我们是怎样通过名为 /nginxconf/default.j2 和/etc/nginx/sites-available/default 的这两个路径的。前者是Jinjia2模板,后者是渲染模板的输出位置。以下是在default.j2文件的内容:


upstream ghost.backend {
{% for container in containers %}
{% if container['state'] == "running" %}
server {{container['primaryIpAddress']}}:2368;
{% endif %}
{% endfor %}
}

server {
listen 80;
server_name ghost_demo;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://ghost.backend;
proxy_redirect off;
}
}


Dockerfile的第4步和第5步在镜像中设置了运行命令“/sbin/my_init”并暴露端口80和443。

现在是时候构建镜像了:


$ docker build -t="pitrho/nginx-rancher-gen-demo" .


第3步 – 创建并部署nginx服务

现在我们已有了nginx镜像,就可以开始向我们在第1步中创建的docker-compose.yml文件中添加nginx服务了。


ghost:
image: ghost
expose:
- "2368"

nginx:
image: pitrho/nginx-rancher-gen-demo:latest
ports:
- 80:80
links:
- ghost
environment:
NGINX_RUN_TYPE: rancher-gen
RANCHER_GEN_HOST: $RANCHER_HOST
RANCHER_GEN_PORT: $RANCHER_PORT
RANCHER_GEN_ACCESS_KEY: $RANCHER_ACCESS_KEY
RANCHER_GEN_SECRET_KEY: $RANCHER_SECRET_KEY
RANCHER_GEN_PROJECT_ID: $RANCHER_GEN_PROJECT_ID
RANCHER_GEN_OPTIONS: --stack demo --service ghost


上述可变的RANCHER_GEN_OPTIONS环境是用于向Rancher-Gen传递附加命令行选项的 。你可以在Rancher-Gen文档中查看这些选项的说明。

现在运行rancher-compose来启动nginx服务:


$ rancher-compose -p demo up -d nginx


此时,ghost和nginx服务都启动并运行了:



而且,将浏览器指向运行中的nginx容器中主机的IP地址,你就可以访问ghost了:



如果你使用shell来检查nginx容器,并打开渲染的文件 /etc/nginx/sites-enabled/default,你将会看到以下的输出:


upstream ghost.backend {
server 10.42.136.216:2368;
}

server {
listen 80;
server_name ghost_demo;

location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://ghost.backend;
proxy_redirect off;
}
}


正如预期的那样,这是在运行rancher-gen命令时的基于指定模板的渲染输出。此时,如果你要升级ghost服务,并再次查看渲染文件,你会发现上游部分下的IP地址已经改变了。

结论

总结来说,Rancher-Gen是一个自动化工具,可用于生成文件,并运行通知命令。借助Jinja2的模板表现力,及其整洁的命令行界面,Rancher-Gen可用于生成大多数配置文件,并自动解决那些对大多数系统管理员和软件工程师而言繁琐和重复的工作。

原文来源:Rancher Labs

docker 反向工程

ozbillwang 发表了文章 • 2 个评论 • 3739 次浏览 • 2016-10-18 18:46 • 来自相关话题

今天参加了一个meetup,演讲者介绍了一个可以模拟docker 命令的工具,可以在不安装docker 的情况下,体验下载镜像,启动容器的功能。 这个工具使用python写的,如果有兴趣花些时间去理解一下,对docker 的深层次理解 ...查看全部
今天参加了一个meetup,演讲者介绍了一个可以模拟docker 命令的工具,可以在不安装docker 的情况下,体验下载镜像,启动容器的功能。

这个工具使用python写的,如果有兴趣花些时间去理解一下,对docker 的深层次理解会有很大的帮助。

# 运行环境

你需要一台linux 系统,本文用centos 7 , 暂时不支持windows 或者 mac os

# 卸载 docker engine

确保docker 命令 不存在。

# 准备环境

yum install -y gcc python-devel net-tools lsof 


# mocker工具的环境安装

$ git clone https://github.com/tonybaloney/mocker.git
$ cd mocker
$ pip install virtualenv
$ virtualenv ENV
$ source ENV/bin/activate
$ pip install -r requirements.txt
$ ./mocker.py help
Usage:
mocker pull []
mocker run
mocker images
mocker (-h | --help)
mocker --version


# 体验 pull 命令

$ ./mocker.py pull nginx
Starting new HTTPS connection (1): auth.docker.io
"GET /token?service=registry.docker.io&scope=repository:library/nginx:pull HTTP/1.1" 200 1442
Fetching manifest for nginx:latest...
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/manifests/latest HTTP/1.1" 200 6938
Fetching layer sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4..
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4 HTTP/1.1" 307 432
Starting new HTTPS connection (1): dseasb33srnrn.cloudfront.net
"GET /registry-v2/docker/registry/v2/blobs/sha256/a3/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4/data?Expires=1476787299&Signature=P0typ2UZrgFdGd4SrfBvKdKefSC8JkkNWxTExzZYTE6luS8R0ZbjCWWBHbwEj5v-q668Vf-y11WBULC7O~SWcYycly7ek0Eemlmr4eGphP-zdEJqCn9Nol~YSqrI4BX~MHFoWgIc9uXUJ1HM8VnTaMXIVELt5lNT9wDRz7OuzU8_&Key-Pair-Id=APKAJECH5M7VWIS5YZ6Q HTTP/1.1" 200 32
...
Fetching layer sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb..
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/blobs/sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb HTTP/1.1" 307 432
Starting new HTTPS connection (1): dseasb33srnrn.cloudfront.net
"GET /registry-v2/docker/registry/v2/blobs/sha256/6a/6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb/data?Expires=1476787301&Signature=d1jSz7Z9Syjlk9OPjzYgFk37f-g9d1OK--2fi4FdotZwbmFTLDrj~TyRFx9WZA10W7DUJ7vL-GFb5WjOIoHWe1CR2dm9NewjvzI-k7gI6CmPeG0F0ZRseaYzanmoYYSIUSPbn2hsbq57pl43i3pJ2NLKbGb2eGuL~YBgjUFsEq0_&Key-Pair-Id=APKAJECH5M7VWIS5YZ6Q HTTP/1.1" 200 51354364
[list]
[*]bin[/*]
[*]bin/bash[/*]
[*]bin/cat[/*]
[*]bin/chacl[/*]
[*]bin/chgrp[/*]
[*]bin/chmod[/*]
[*]bin/chown[/*]
[*]bin/cp[/*]
[*]bin/dash[/*]
[*]bin/date[/*]
[/list]...
Fetching layer sha256:2fbd37c8684bca3df2090b8b8acce020837d560ec8917f25714e45e7d1f4611e..
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/blobs/sha256:2fbd37c8684bca3df2090b8b8acce020837d560ec8917f25714e45e7d1f4611e HTTP/1.1" 307 432
Starting new HTTPS connection (1): dseasb33srnrn.cloudfront.net
"GET /registry-v2/docker/registry/v2/blobs/sha256/2f/2fbd37c8684bca3df2090b8b8acce020837d560ec8917f25714e45e7d1f4611e/data?Expires=1476787399&Signature=DNXOXyA9an018bG25GtQMaErpQwOtZUgMVW2Czur1DbwqJLe-w-5ETapnDVlz7WksCXNZ9JaO-hMBv~UjOOwQD1cjnpm3-QVMWGsnS4TBHLA9YZGx8wUMlUyQonSvHRTZKI2vr-SMlPDe91WgUzA-OrywSNvAXEqdIm-sn5qvPE_&Key-Pair-Id=APKAJECH5M7VWIS5YZ6Q HTTP/1.1" 200 195
[list]
[*]var[/*]
[*]var/log[/*]
[*]var/log/nginx[/*]
[*]var/log/nginx/access.log[/*]
[*]var/log/nginx/error.log[/*]
[/list]...
Fetching layer sha256:20a0fbbae14864e06e14f89126551d004555d9e2c13591105862ca1f9a418e9d..
Starting new HTTPS connection (1): registry-1.docker.io
"GET /v2/library/nginx/blobs/sha256:20a0fbbae14864e06e14f89126551d004555d9e2c13591105862ca1f9a418e9d HTTP/1.1" 307 432
Starting new HTTPS connection (1): dseasb33srnrn.cloudfront.net
"GET /registry-v2/docker/registry/v2/blobs/sha256/20/20a0fbbae14864e06e14f89126551d004555d9e2c13591105862ca1f9a418e9d/data?Expires=1476787401&Signature=H-52MjpIcEpNWHyikqDB50rrv1nj-4wPON6jW0gK5OeLlguxAv2iSUZpnQ1ImL-ixTxhD0iLdCpzMNDLsZ2lagJOVM6Susd1Jn-l7N8EgXUBkwQWAejbsJTjV89O6cI7T60OzaWsBPQCbM2jPYyFsfMPCjb8jLahSGuu95Wy2iw_&Key-Pair-Id=APKAJECH5M7VWIS5YZ6Q HTTP/1.1" 200 20134306
[list]
[*]etc[/*]
[*]etc/alternatives[/*]
[*]etc/alternatives/rename[/*]
[*]etc/alternatives/rename.1.gz[/*]
[*]etc/apt[/*]
[*]etc/apt/sources.list[/*]
[*]etc/apt/trusted.gpg[/*]
[*]etc/apt/trusted.gpg.d[/*]
[*]etc/apt/trusted.gpg~[/*]
[*]etc/ca-certificates[/*]
[/list]...


可以看到,其进程是: 授权,获得镜像清单 ($HOME/mocker/library_nginx.json),获取每层镜像。我们也可以看到docker 镜像服务器使用了aws 的 CDN 服务来加速下载。

在pull 镜像的时候,该作者给出很多有用的信息。 给出具体的api 命令及链接,每个docker 层的sha256码,每层的文件列表,等等

下载的镜像层文件(tar 包)被存放在 $HOME/mocker 目录下了。 并且里面的文件全部展开。

 tree $HOME/mocker/library_nginx


# 列出镜像

 $ ./mocker.py images
+---------------------+---------+---------+--------------------------+
| name | version | size | file |
+---------------------+---------+---------+--------------------------+
| library/hello-world | latest | 1006.0B | library_hello-world.json |
| library/nginx | latest | 68.2MiB | library_nginx.json |
+---------------------+---------+---------+--------------------------+


# 运行 容器

    $ python mocker.py run library/nginx
Creating cgroups sub-directories for user vagrant
Hierarchies availables: ['hugetlb', 'perf_event', 'freezer', 'cpuset', 'net_cls', 'memory', 'blkio', 'cpuacct', 'cpu', 'cpu,cpuacct', 'devices', 'systemd']
cgroups sub-directories created for user vagrant
Creating cgroups sub-directories for user root
Hierarchies availables: ['hugetlb', 'perf_event', 'freezer', 'cpuset', 'net_cls', 'memory', 'blkio', 'cpuacct', 'cpu', 'cpu,cpuacct', 'devices', 'systemd']
cgroups sub-directories created for user root
Running "nginx -g "daemon off;""
Creating cgroups sub-directories for user root
Hierarchies availables: ['hugetlb', 'perf_event', 'freezer', 'cpuset', 'net_cls', 'memory', 'blkio', 'cpuacct', 'cpu', 'cpu,cpuacct', 'devices', 'systemd']
cgroups sub-directories created for user root
Setting ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Setting ENV NGINX_VERSION=1.11.5-1~jessie


# 启动网络

另开一个窗口:

$ ip netns
netns_c_9052 (id: 0)

$ ip netns exec netns_c_9052 ifconfig
lo: flags=73 mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10
loop txqueuelen 0 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

veth1_c_9052: flags=4163 mtu 1500
inet 10.0.0.103 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::42:acff:fe11:90 prefixlen 64 scopeid 0x20
ether 02:42:ac:11:00:90 txqueuelen 1000 (Ethernet)
RX packets 8 bytes 648 (648.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8 bytes 648 (648.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

$ ip netns exec netns_c_9052 lsof -naP -i tcp:80
lsof: no pwd entry for UID 104
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 12006 root 26u IPv4 34170 0t0 TCP *:80 (LISTEN)
lsof: no pwd entry for UID 104
nginx 12007 104 26u IPv4 34170 0t0 TCP *:80 (LISTEN)



到这一步,可以看到,80 端口已经启用。

# 确认容器里的web服务运行

$ ip netns exec netns_c_9052 curl localhost:80



Welcome to nginx!



Welcome to nginx!


If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.



For online documentation and support please refer to
nginx.org.

Commercial support is available at
nginx.com.



Thank you for using nginx.





# 参考

repo: https://github.com/tonybaloney/mocker

ip-netns 命令参考 : http://man7.org/linux/man-pages/man8/ip-netns.8.html

如何在Python中使用ZeroMQ和Docker构建微服务架构

Leo_li 发表了文章 • 0 个评论 • 14693 次浏览 • 2016-02-28 18:43 • 来自相关话题

@Container容器技术大会将于6月4日在上海光大会展中心国际大酒店举办,来自携程、PPTV、蚂蚁金服、京东、浙江移动、海尔电器、唯品会、eBay、道富银行、麻袋理财等公司的技术负责人将带来实践经验分享,3月21日之前购票只需238元,欢迎感兴趣的同学抢购 ...查看全部
@Container容器技术大会将于6月4日在上海光大会展中心国际大酒店举办,来自携程、PPTV、蚂蚁金服、京东、浙江移动、海尔电器、唯品会、eBay、道富银行、麻袋理财等公司的技术负责人将带来实践经验分享,3月21日之前购票只需238元,欢迎感兴趣的同学抢购。
#微服务是什么?
微服务是一种架构风格,它包括多个彼此间进行通信的独立进程。在设计上,这些进程具有高度的可扩展性、相互解耦而且一次只完成一个较小的任务。这些服务都拥有自己的资源以及通过网络实现彼此间通信的进程。

相比于靠后端的单体结构来封装所有服务器逻辑的传统客户端-服务器架构(C/S架构)而言,微服务架构的差异性体现在关注点分离(Separation of concern)。这种设计模式更易于维护,使得灵活性、可扩展性及容错能力更强。但是这种分布式架构所的不足之处体现在如果设计不合理就会使得排错及维护变得复杂。
#一个简单微服务的例子
让我们来分析这样的一个场景:你正在使用微服务模式构建一个电子商务网店。

对于一个电商网店上的常见商品,好比说iPhone,其详情页会显示:

  • 产品的及基本信息
  • 你的购买历史
  • 哪些人买了iPhone也买了手机套
  • 与苹果手机相关的优惠和折扣
  • 店家的数据
  • 送货方式
  • 推荐商品等等
此外,这个简单的产品详情页的接口将有多个版本的来匹配Web、移动端以及用于第三方应用程序的REST API。在微服务模式中数据分布在多个服务之间。在这个例子中,服务包括:
  • 产品详情服务
  • 商家服务
  • 支付服务
  • 优惠及折扣服务
  • 库存服务
  • 定价服务
  • 回顾服务
  • 推荐服务
这些独立的服务是如何被访问的呢?解决办法是使用一个API网管,它作为所有客户端的单一入口并且根据需求调用分布在整个基础架构中的特定微服务。以上模式的行业应用案例是NetFlix API网关,它具有支持不同设备的多个API客户端。你可以点击此处了解更多。##构建一个简单的微服务目前有很多方法可以用于构建你的微服务。在本文中我们将使用ZeroMQ来创建两个进程之间的通信。ZeroMQ提供了用于在套接字之上开发可扩展、分布式systed的构建块。它使用椭圆曲线密码体制(第四版)来实现安全性,并提供了即刻开启的通讯模式。关于ZMQ,还有很多优点。MQ即是针对异步工作而设计的线程化消息队列。谈论太多zeroMQ的内容已经超出了本文的范畴,你可以阅读使用zeromq以及zeromq用于分布式系统。我们要使用的另一个工具是Docker。本文假设读者对Docker已经有了基础的了解。ZeroMQ有很多种通讯模式,为了开始我们的工作,让我们用ZeroMQ和Flask来配置一个简单的PUB-SUB。下图展示了组件之间的关系和数据流。
microservices-docker-python-apcelent.png
  • 1&3 - 一个flask服务器运行在5000端口上而且其URL是`/downcase/`。该URL用来接受(GET)请求,而所有格式为的请求将收到回应:答谢字符将会转换为小写字符并返回。
  • 2 - 回应的消息也被发送给同一个容器中的ZMQ发布者(Publisher)
  • 4,5 - ZMQ订阅者(subscriber)持续监听并将来自ZMQ服务器的消息保存到名为`subscriber.log`的文件中

##创建服务器
首先看一下我们的Dockerfile
FROM ubuntu:14.04 
RUN apt-get update
RUN apt-get install -y --force-yes python python-dev python-setuptools software-properties-common gcc python-pip
RUN apt-get clean all

RUN pip install pyzmq

RUN pip install Flask

ADD zmqserver.py /tmp/zmqserver.py


# # Flask Port

EXPOSE 5000


# # Zmq Sub Server

EXPOSE 4444

CMD ["python","/tmp/zmqserver.py"]

我们选择Ubuntu 14.04作为容器操作系统。我们安装了基本的软件包。通过pip,我们安装pyzmq(zeromq的Python绑定)同时也安装了Flask。接着我们导出端口5000(flask服务器)和4444(发布者运行的端口)。此外,我们复制了包含所有flask及zeromq pythond代码的脚本文件`zmqserver.py`并运行它。

现在我们来看一下zmqserver.py的内容:
# server.py 
import time
import zmq

HOST = '127.0.0.1'
PORT = '4444'

_context = zmq.Context()
_publisher = _context.socket(zmq.PUB)
url = 'tcp://{}:{}'.format(HOST, PORT)

def publish_message(message):
try:
_publisher.bind(url)
time.sleep(1)
_publisher.send(message)

except Exception as e:
print "error {}".format(e)
finally: _publisher.unbind(url)

from flask import Flask
from flask import request
app = Flask(__name__)

@app.route("/downcase/", methods=['GET'])
def lowerString():

_strn = request.args.get('param')
response = 'lower case of {} is {}'.format(_strn, _strn.lower()) publish_message(response)
return response

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False)

ZMQ发布者运行在4444端口上。我们创建了一个context并且声明了URL。我们运行了flask app,它通过URL `/downcase/`把GET获得的参数`Param`转换成小写字符,这就是服务的应答。应答的字符串是`published`,它作为一个消息把相同的字符串返回给浏览器。

为了构建以上的Docker映像(image),我们执行以下的命令:
`sudo docker build -t docker-zmq-pub`
并且在该映像之上执行:
`docker run --name docker-pub-server -p 5000:5000 -p 4444:4444 -t docker-zmq-pub`。

我们把容器中的端口5000和4444映射到这台主机上,于是无论客户端在哪里,它们都可以订阅这个发布者。
##订阅者客户端
# client.py
import zmq
import sys
import time
import logging
import os

HOST = '127.0.0.1'
PORT = '4444'

logging.basicConfig(filename='subscriber.log', level=logging.INFO)


class ZClient(object):

def __init__(self, host=HOST, port=PORT):
"""Initialize Worker"""
self.host = host
self.port = port
self._context = zmq.Context()
self._subscriber = self._context.socket(zmq.SUB)
print "Client Initiated"

def receive_message(self):
"""Start receiving messages"""
self._subscriber.connect('tcp://{}:{}'.format(self.host, self.port))
self._subscriber.setsockopt(zmq.SUBSCRIBE, b"")

while True:
print 'listening on tcp://{}:{}'.format(self.host, self.port)
message = self._subscriber.recv()
print message
logging.info(
'{} - {}'.format(message, time.strftime("%Y-%m-%d %H:%M")))

if __name__ == '__main__':
zs = ZClient()
zs.receive_message()

我们声明了发布者的IP地址及端口,当前它运行在同一个的主机上因此地址是127开头。我们在URL `tcp://IP:PORT`上进行监听。一旦我们收到一个消息,就将其附上时间戳后记录到名为`subscriber.log`的文件中。运行客户端要做的所有工作就是执行 `python .py`。如果你在以上的架构上进行构建,它可以很好地充当近实时的日志聚合引擎。

我在Unbuntu主机上对以上的代码进行了测试。这里所用的代码保管在GitHub上。这是一个如何配置ZMQ、Docker和Python服务器的基础讲解,在我的下一片文章中我们会使用我们已经学习的东西构建简单的微服务。

希望这篇文章对你有所帮助。

原文链接:How to Setup Microservices Architecture in Python with ZeroMQ & Docker(翻译:李毅)

使用Docker部署Python应用的一些最佳实践

codesun 发表了文章 • 0 个评论 • 22630 次浏览 • 2015-02-06 00:23 • 来自相关话题

【编者的话】本篇文章源自作者团队在长期开发过程中总结的宝贵经验,其中Supervisor、Gunicorn以及Nginx更是在使用Python开发Web应用时最常用的软件,因此对于打算使用Docker部署Python应用的读者而言,这些最佳实践是很有参考价值。 ...查看全部
【编者的话】本篇文章源自作者团队在长期开发过程中总结的宝贵经验,其中Supervisor、Gunicorn以及Nginx更是在使用Python开发Web应用时最常用的软件,因此对于打算使用Docker部署Python应用的读者而言,这些最佳实践是很有参考价值。同时希望各位在日常实践过程中,也能将各自踩到过的“坑”以及宝贵的经验分享出来,大家共同进步!


我们可以使用Docker简单而高效的部署Python应用,同时,也有一些最佳实践来帮助我们愉快的完成部署。当然,也不是说这些最佳实践就是完成部署的唯一方式,只不过我们团队发现它们具有高可用性,并且容易维护。注意本文中大多数内容都只是代表我的立场,基于Docker的实现方式有很多,你可以随便选择。本文中我不会过多的介绍Volume,因为它可能需要一个单独的话题来解释。我们通常会使用Volume将源代码复制到容器中,而不是在每次运行时都重新构建。
## DEBIAN_FRONTEND
Docker用户应该都很熟悉这个环境变量,它告知操作系统应该从哪儿获得用户输入。如果设置为"noninteractive",你就可以直接运行命令,而无需向用户请求输入(译者注:所有操作都是非交互式的)。这在运行apt-get命令的时候格外有用,因为它会不停的提示用户进行到了哪步并且需要不断确认。非交互模式会选择默认的选项并以最快的速度完成构建。

请确保你只在Dockerfile中调用的RUN命令中设置了该选项,而不是使用ENV命令进行全局的设置,因为ENV命令在整个容器运行过程中都会生效,所以当你通过BASH和容器进行交互时,如果进行了全局设置那就会出问题。例子如下:

# 正确的做法 - 只为这个命令设置ENV变量
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python3
# 错误地做法 - 为接下来的任何命令都设置ENV变量,包括正在运行地容器
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get install -y python3

## requirements.txt
相比于基本的代码(codebase),应用的依赖很少发生变化,因此我们可以在Dockerfile中安装项目依赖,这也可以加快之后的构建速度(之后的构建只需要构建变更的代码)。Docker容器的层级构建可以缓存依赖安装的过程,所以之后的的构建速度会非常快,因为它不需要重新下载和构建依赖。
## 文件顺序
按照上面的思路(利用缓存)来推断,文件添加到容器的顺序至关重要。我们应该把频繁变更的文件放置到Dockerfile的下方,以便充分使用缓存来加速Docker的构建过程。例如,应用配置、系统配置和依赖都很少改变,我们就可以把它们放到Dockerfile的顶部。而源文件,比如路由文件、视图(views)和数据库代码会经常发生改变,所以我们就可以把它们放在Dockerfile的下方,注意是Docker配置命令(EXPOSE、ENV等)的下方。

另外,不要考虑如何将你的文件拷贝到Docker,它不会加快你的构建速度,因为大多数的文件根本不会用到,比如应用源文件。
## 应用密钥
之前我们一直不知道如何把应用密钥安全的传递给应用,后来我们发现可以使用`docker run`命令中的`env-file`参数。我们会把所有的密钥和配置都放在app_config.list文件中,然后通过这个文件交付给应用。具体如下:

docker run -d -t -—env-file app_config.list

这个方法允许我们简单地改变应用设置和密钥,而无需重建一个容器。

注意:请务必确保app_config.list在.gitignore文件的记录中,不然它不会被检录到源文件中。
## Gunicorn
我们使用Gunicorn作为容器内部的应用服务器,Gunicorn非常的稳定并且性能很好,它有非常多的配置选项,比如指定worker数量和类型(green threads、gevent等)的能力,你可以根据负载来调整应用,以获得最佳性能。

启动Gunicorn很简单:

# 安装
pip3 install gunicorn

# 运行服务器
gunicorn api:app -w 4 -b 127.0.0.1:5000

最后是在Nginx后面运行你的应用服务器,这样你可以进行负载均衡。
## supervisord
你是不是想过在容器中运行多个进程?我想Supervisord绝对是你的最佳辅助工具。假设我们想部署这样一个容器,它包含Nginx反向代理以及Gunicorn应用。你通过BASH脚本可能就能实现,但是让我们想更加简洁一点。

Supevisor是“一个进程控制系统,它支持用户在类UNIX操作系统上,监视并控制一些进程”。听起来很完美!你需要先在你的Docker容器内安装Supervisor。

RUN DEBIAN_FRONTEND=noninteractive apt-get install -y
supervisor

为了让Supervisor知道该运行什么以及如何管理进程,我们接下来需要为它写一个配置文件。

[supervisord]
nodaemon = true # 这个会让supervisord运行在前端

[program:nginx] # 你想运行的第一个程序的命名
command = /usr/sbin/nginx # nginx可执行程序的路径
startsecs = 5 # 如果nginx保持开启5s,我们视为启动成功

[program:app-gunicorn]
command = gunicorn api -w 4 -b 127.0.0.1:5000
startsecs = 5

这是非常基本的配置,它还有很多的配置项,比如控制日志、stdout/stderr重定向、重启策略等。这个工具真不错。

一旦你完成了配置,请确保Docker将其复制到了容器中。

ADD supervisord.conf /etc/supervisord.conf

让Supervisor作为容器的自启动命令。

CMD supervisord -c /etc/supervisord.conf

它将会在容器启动的时候,运行Gunicorn和Nginx。如果已经配置过了,那将会按需重启它们。
## 学到的东西以及未来的目标
我们已经花了很长时间在Docker中部署代码,并且接下来会投入更多的时间。在使用Docker的过程中,我们学到的最重要经验就是如何最小化思考(think minimally)。在一个容器中运行你的整个系统真的很诱人,但是在应用各自的容器中运行应用进程却更容易维护。一般情况下,我们会在容器中运行Nignx和Web服务器,并且在一些场景中,使用单独的容器来运行Nginx却没有任何优势,它可能只会增加复杂度。我们发现对于大多数情况,它在容器中的开销是可接受的。

我希望这些信息对各位有价值!当我们团队学到更多最佳实践时,我会更新这篇文章。

原文链接:Deploying Python with Docker(翻译:孙科 校对:李颖杰)

====================
译者介绍
学生一枚,撸代码站点维护者,专注于Linux系统编程和编译技术。热衷于学习各种语言和技术。目前正在开发一个Java Web框架(毕设)和一个web服务器(D语言编写)。
Python是一种面向对象、直译式的电脑编程语言,具有近二十年的发展历史。它包含了一组功能完备的标准库,能够轻松完成很多常见的任务。它的语法简单,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。