记基于docker+gunicorn部署sanic项目遇到的很多很多坑
前言:
最近有个项目需要上线,是python中sanic网络异步框架写的,并且要求使用docker+nginx来部署项目实现负载均衡,于是乎百度了sanic项目部署,基本上都是基于docker+gunicorn部署sanic项目这篇文章,里面讲的也稍稍微有些简略,不过对于小白特别不友好,按步骤操作肯定是不行的,因为文章中只举了很小很小的一个例子,感觉更像demo。而小白可能只是临时接受部署任务,按部就班的操作是会出现很多错误的。现在就来排排坑。(建议先看一遍再动手部署)
一、Dockerfile文件放在哪?
这是sanic项目的总目录,首先明确一点我的主运行文件是run.py,所以Dockerfile需要放在和我主文件相同地方,这样执行build指令就可以直接在当前目录下创建。可以直接使用vim指令在该项目目录下创建Dockerfile,记住文件名一定要相同
二、Dockerfile里面写什么?为什么要写
FROM taoliu/gunicorn3
WORKDIR /temp1
ADD . /temp1
RUN pip install --upgrade pip
RUN pip install -r requirements.txt -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
EXPOSE 9010
CMD gunicorn run:app --bind 0.0.0.0:9010 --worker-class sanic.worker.GunicornWorker
下面一个个来解释
FROM:表示这个镜像是基于什么创建的,也就是基础镜像,这里使用taoliu/gunicorn3表示我使用的是gunicorn3来运行我的镜像,可以理解为项目需要一个环境来运行。而这个镜像是国内的,所以下载会较快
WORKDIR:表示工作目录是在docker中的哪里,可以稍微理解一下docker是个独立的系统,就像安装在Windows中虚拟机虚拟的linux,和外面的Windows命令和目录是不相互关联的。而这个目录也是我们后面执行命令所在的目录。
ADD:这里使用ADD . /temp1,前面的“.”表示将当前所在目录的所有文件全部放到docker中的/temp1这里要和上面的WORKDIR设置的一样,命名按照自己喜欢的来,这里是测试就用temp1代替
RUN:就相当于开始运行指令(可以理解为在linux中输入命令行,但其实这是在docker环境中输入的)了,docker中的系统一些python依赖的库是不存在的,所以我们开始先更新一下docker中的pip,再由pip获取项目所需镜像,这里我整理到了固定的文件内即requirements.txt,推荐使用国内镜像。这样下载较快,我使用的是阿里云镜像。
阿里云:http://mirrors.aliyun.com/pypi/simple/
豆瓣:http://pypi.douban.com/simple/
清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/
中国科学技术大学:http://pypi.mirrors.ustc.edu.cn/simple/
华中科技大学:http://pypi.hustunique.com/
EXPOSE:设置镜像暴露端口,记录容器启动时监听哪些端口,容器启动时,Docker Daemon会扫描镜像中暴露的端口,如果加入-P参数,Docker Daemon会把镜像中所有暴露端口导出,并为每个暴露端口分配一个随机的主机端口(暴露端口是容器监听端口,主机端口为外部访问容器的端口)
注意:EXPOSE只设置暴露端口并不导出端口,只有启动容器时使用-P/-p才导出端口,这个时候才能通过外部访问容器提供的服务
CMD:设置容器的启动命令,也就是当我们启动容器的时候执行的命令,我的项目端口是0.0.0.0:9010,文件是run,所以使用gunicorn3启动时,命令为上述文件中。需要注意的是Dockerfile中只能有一条CMD命令,如果写多了则最后一条生效
三、build镜像
上面的步骤处理完之后就可以build出新镜像了,使用下面命令:
sudo docker build -t sanic_item .
这里千万要加后面的点,代表当前路径下创建镜像
四、使用run还是使用create+start
在对于新技术肯定要经过较多的测试才能很好的掌握,如果使用下面这条命令:
sudo docker run --name sanic1 -p 8080:8080 sanic_item
--name:为容器起个别名,这样可以使用这个别名操作容器,而不需要用随机的容器ID来进行操作
-p:来指定端口,前面的端口是linux的,后面的端口是docker中项目运行的端口,两个可以一样可以不一样。但是为了方便开发及测试,建议尽量一样.
后面就是镜像名了。而这条run语句就等于create+start即创建并开启容器。这里说一下容器是基于镜像来运行的。关于容器和镜像的关系要深入docker中了解。这里不详细介绍。这条语句的弊端就是第一次使用的部署可以使用,但是如果第二次还使用,那么就会不停的创建新容器。在不了解的情况下,以为run指令只会运行镜像。没想到docker中是根据镜像来创建容器再运行。滥用run指令的结果图:
可以看出很多是基于一个镜像来创建的容器。其实第一次使用完run之后便可使用一些经常使用的命令:
sudo docker stop sanic1 #停止当前运行的容器,前提是run指令时有--name来指代名字
sudo docker start sanic1 #只需要启动就行,不需要再用run或create
sudo docker ps #查看当前运行镜像
sudo docker ps -a #查看当前容器状态,正在运行的容器PORTS列会有参数,上图就是没有容器正在运行
sudo docker rmi 镜像ID/别名 #需要先使用ps指令找到要删除的镜像ID再删除
sudo docker rm 容器ID/别名 #比如删除sanic1这个容器sudo docker rm sanic1
五、使用nginx实现负载均衡
首先明确一点一个端口不能同时被两个程序所占用。就打个比方来说,现在的项目前端发来的请求访问的是8080端口,如果是单机版项目,直接运行监听8080即可。但是目前遇到的情况是项目部署在docker上,还要启动nginx来监听。也就会造成这个端口有两个进程在监听,这时候只能启动一个。并且我们要部署多个后台服务。所以首先考虑nginx。我的nginx配置文件如下(注意看中文注释部分)
http { #代表HTTP协议其他的协议需要其他定义
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' #这里规定的是日志的输出格式,建议打开
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server{
listen 8080;#监听8080端口
server_name localhost;
access_log /usr/logs/host1.access.log main;#开启日志功能,只要访问这个端口的都会有输出,具体的名字和目录自己指定,main为上面定义的日志格式
location /{
proxy_pass http://localhost; #对于发送到8080端口的请求可以进行转发
}
}
server { #nginx默认服务,这里只需要放入前端界面即可
listen 80;
server_name localhost;
#charset koi8-r;
access_log /usr/logs/host.access.log main; #不同端口我建立了不同日志,必须要保证日志所在目录有效才会创建,同时日志我这里没设置无缓存刷出,若要看见实时消息的才设置
root /usr/local/dist2/;
# location / {
# root html;
# index index.html index.htm;
location /{
index index.html index.htm;
}
}
upstream localhost{#这里是实现负载均衡的策略,我的上一篇博客中介绍过为什么使用这种方法
ip_hash;
server 0.0.0.0:9010 weight=1;
server 0.0.0.0:9011 weight=1;
server 0.0.0.0:9012 weight=1;
}
}#这里代表HTTP协议的请求即定义的服务完成
可以看到我们对于8080端口的请求,转发到了9010,9011,9012三个端口,那么docker需要准备三个不同镜像,来创建三个不同容器,这样才能实现nginx的代理。对于sanic项目只需要改一下运行端口即可,其余部分相同,Dockerfile中的端口也要与其对应。比如我当前的sanic项目改了三个不同端口为9010,9011,9012。(对应上面讲的Dockerfile也要变),然后build三个不同镜像为sanic_item1,sanic_item2,sanic_item3。然后启动三个命令窗口输入下面的指令
sudo docker run --name sanic1 -p 9010:9010 sanic_item1
sudo docker run --name sanic2 -p 9011:9011 sanic_item2
sudo docker run --name sanic3 -p 9012:9012 sanic_item3
便能运行三个容器了。
注意:这里介绍的是纵向扩展的负载均衡架构,也是一个介绍。如果多台其实只要改ip地址即可,端口可以不改变。这里所说的docker+nginx实现负载均衡,不需要Nginx部署在docker内部,Nginx只是一个请求转发的工具,如果是遇到上千万级请求或上亿级,超过了nginx的负荷才需要部署多台。主要的压力还是在后端和前端处理和解析数据上。
总结:
这篇博客主要是目前使用sanic项目部署的资料特别少,而这么少的资料中整理起来,以及遇到的一些bug很难查找。所以稍微讲了一下部署项目遇到的坑和解决方案。建议先通读一遍,脑子里大概有个印象再上手部署。
补充一点:为什么要部署在docker上,因为Docker有点像Git,也就是你创建完的镜像,可以上传到DockerHub中让别人或者开发组的其他人下载,实现一次部署就能到处运行,emmm,我觉得这才是最主要的。不然上述部署过程完全能够在linux中实现。