记基于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中实现。

 

 

 

posted @ 2019-08-22 17:43  方块人  阅读(1857)  评论(0编辑  收藏  举报