Linux Crontab定时任务必备招式介绍

crontab 简介

这里的伙伴大多数做客户端开发的,可能对服务端相关的内容关注的相对少一些。 crontab 是这样一个工具,他能够根据你给出的配置在指定的时间执行任务。比如定期删除过期的日志文件(很多服务端环境,会生成大量的日志文件,在你不经意间就会把你的硬盘填满)。当然,它的应用不仅限于服务端,对于自己本地的电脑来说,也有它的用处。

crontab 是 Unix 系统的标配,几乎我们常见的大多数类 Unix 系统都自带 crontab, 包括 MacOS。 Crontab 名字的来源是 chronos – 一种极客们对时间的叫法。 关于它的更多内容,大家可以参考 Wikipedia 上面的描述:https://en.wikipedia.org/wiki/Cron。

使用方法

简单做了介绍之后,咱们来看看怎么使用。 大家可以在自己电脑的命令行中输入:

$ crontab -l

这个命令, 然后你应该会看到类似的输出:

crontab: no crontab for xxx

这个命令是用于列出你电脑上已有的计划任务,因为我们现在还没有指定任何计划任务,所以这个输出就像上面那样,告诉我们没有计划任务。 如果想添加自己的计划任务,可以输入这个命令:

$ crontab -e

这个命令会用 vi 打开一个命令行编辑器,你可以在这里编辑计划任务的描述。 crontab 描述文件中每一行代表一个任务,每一行的格式如下:

* * * * * rm /home/someuser/tmp/*

前面 5 个星号代表时间设置,从左到右每个星号的位置分别对应

分钟(0-59) 小时(0-23) 每月中的天(1-31) 每年中的月(1-12) 每周中的星期几(0-6) 0 代表星期日

每个星号的位置,可以是星号本身,也可以是具体的数字。 如果是星号本身,代表不加限制。 如果是数字,代表指定的时间。 举几个例子:

*  *   *  *  *    // 5个都是个星号,代表每分钟都会执行。 
30 *   *  *  *    // 每到 30分的时候执行一次,也就是每小时执行两次。
*  18  *  *  *    // 每天的 18 点执行一次。
*  */2 *  *  *    // 每隔 2 小时执行一次, */ 是间隔时段的表示法。

以上是 crontab 时间设置个几个常用示例, 只要按照我们上述的每隔时间点的规则来使用,几乎可以满足我们绝大部分计划任务的需求。

在时间配置之后, 就是我们要执行的脚本文件,比如我们想在每天早上 8 点的时候清空机器上的临时文件,就可以这样配置:

* 8 * * * rm /home/someuser/tmp/*

每天 8点的时候,就会执行后面的 rm 命令, 删除临时文件夹中的文件。 后面的这个命令行内容,可以是任何的 Shell 命令,包括 pipe 操作,比如:

* 8 * * * rm /home/update.sh > /var/log/update.log

这个配置会在每天 8 点的时候, 执行一个叫做 update 的脚本, 并将日志输出流重定向到 update.log 文件中。 这时我们的命令行界面应该是这样的:

未分类

编辑完成后, 按下 Esc 键, 然后输入 :wq(代表保存并退出,这个是 vi 文本工具的使用方法), 在命令行左下角是下面图片中的状态的时候,按下回车键:

未分类

这样就保存了我们刚刚编辑的任务列表,我们再输出 crontab -l 命令,可以看到任务被成功添加了。

这样就 ok 了。剩下的事情就交给 crontab 来处理了。

结语

crontab 是 Unix 系统中标配的计划任务工具,它的原理并不复杂,crontab 是一个守护进程,不停的检测它的任务列表是否符合执行条件,如果符合,就执行任务。这里跟大家聊的都是 crontab 比较常规的用法,更详细的文档,大家可以通过 man crontab 命令自行查看。相信它对大家的日常工作还是比较有帮助的。

使用Jenkins Ansible Docker Swarm实现自动化编译部署

自动化部署在项目部署过程中很重要,一旦自动化部署完成,我们就可以减轻我们手动的操作的步骤以及出错的概率。下面介绍一种通用的自动化部署。从打包编译到上线,一条命令就完成所有操作。简单而高效

1、Jenkins部署这里不在赘述,直接从新建项目开始。

项目截图,如下:增加两个变量如图,

  • BranchTobuild 默认是master分支,在编译的时候也可以指定分支。

  • PushDockerImage 默认布尔值是选中,直接把镜像推到镜像仓库。

未分类

增加Pipeline配置,主要是分了几个步骤。所以用pipeline,也可以不用这种方式,直接脚本也行

所有执行步骤写入Jenkinsfile中,并把文件放入项目的根目录,这样才能调用。

未分类

Jenkinsfile内容如下:包括编译、打包、推送docker镜像到仓库,ansible部署。

#cat Jenkinsfile
pipeline {

   //定义了执行jenkins的机器
    agent {
        label ‘master’
    }
    stages {
        stage (‘Prepare’) {
            steps {
                sh “echo Prepare”
                //准备阶段,没有暂不处理
            }
        }

        //build打包过程,最后会生产apk包
        stage (‘build’) {
            steps {
                sh ‘echo “start Build”‘
                script {
                    if (params.buildDebug) {
                        sh ‘echo “build debug version”‘
                        sh ‘chmod +x ./gradlew’
                        sh “./gradlew clean”
                        sh “./gradlew build –debug”
                    } else {
                        sh ‘echo “build release version”‘
                        sh ‘chmod +x ./gradlew’
                        sh “./gradlew clean”
                        sh “./gradlew build”
                    }
                }
            }
        }
        stage (‘Test’) {
            steps {
                sh “echo Test”
                //测试阶段,没有暂不处理
            }
        }

       //发布阶段,项目中包括了gradle.properties文件,几个变量:版本、项目、产品,按照这几个名词生产镜像的名
        stage (‘Deploy’) {
            steps {
                sh ‘echo “Start Docker Build And Push Images”‘
                script {
                    if (params.PushDockerImage) {
                        def props = readProperties  file: ‘gradle.properties’
                                    def VERSION = props[‘version’]
                                    def PRODUCT = props[‘product’]
                                    def ARTIFACT = props[‘artifact’]
                        sh ‘echo “start Build”‘
                                    //开始修改Dockerfile
                                    sh “sed -i ‘s#${PRODUCT}#${PRODUCT}#g’ Dockerfile”
                                    sh “sed -i ‘s#${VERSION}#${VERSION}#g’ Dockerfile”
                                    sh “sed -i ‘s#${ARTIFACT}#${ARTIFACT}#g’ Dockerfile”
                        sh “docker build -t registry.sreop.com:5000/${PRODUCT}/${ARTIFACT}:${VERSION} -f Dockerfile .”
                        sh ‘echo “Publish Images To registry.leautolink.com”‘
                        sh “docker push registry.leautolink.com:5000/${PRODUCT}/${ARTIFACT}:${VERSION}”

                        //ansible playbook 部署到线上或者测试环境
                        sh “sudo /usr/bin/ansible-playbook  /data/base-docker-compose/product/light/prod/playbook.yml”
                    }
                }
            }
        }
    }
}

2、每个项目需要一个Dockefile

cat Dockerfile
# 基础镜像
FROM registry.sreop.com:5000/alpine-java:8u121b13_jdk_unlimited
# 维护者信息
MAINTAINER [email protected]
# 镜像操作命令
RUN mkdir -p /data/bin/${PRODUCT}/${ARTIFACT}
# 指定后续命令的执行目录
WORKDIR /data/bin/${PRODUCT}/${ARTIFACT}
# 对外连接端口号
EXPOSE 12429
# 向镜像中增加文件
ADD ./build/libs/${ARTIFACT}-${VERSION}.jar .
# 容器启动命令
CMD java -Djava.security.egd=file:/dev/./urandom -jar ${ARTIFACT}-${VERSION}.jar –spring.profiles.active=prod

3、写ansible playbook,为推送到Docker集群平台,并部署到线上或者测试平台。

注意:docker-compose.yml要拷贝到每个docker节点机器上。功能是:获取各个变量,拉取镜像。最后生产docker services

– name: light Pull Image
  hosts: docker-swarm-prod
  remote_user: root
  tasks:
    – name: get product var 
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml |head -n1|awk -F’/’ ‘{print $2}’
      register: product
    – name: get artifact var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#1#g’
      register: artifact
    – name: get version var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#2#g’
      register: version
    – name: docker pull image
      shell: docker pull registry.leautolink.com:5000/”{{ product.stdout }}”/”{{ artifact.stdout }}”:”{{ version.stdout }}” || /bin/true

– name: Remove Docker Old Verison
  hosts: docker-swarm-prod-lead 
  remote_user: root
  tasks:
    – name: get product var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1|awk -F’/’ ‘{print $2}’
      register: product
    – name: get artifact var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#1#g’
      register: artifact
    – name: get version var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#2#g’
      register: version
    – name: remove light
      shell: docker stack rm {{ artifact.stdout }} || /bin/true

– name: Start New verison
  hosts: docker-swarm-prod-lead 
  remote_user: root
  tasks:
    – name: get product var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1|awk -F’/’ ‘{print $2}’
      register: product
    – name: get artifact var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#1#g’
      register: artifact
    – name: get version var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#2#g’
      register: version
    – name: start light
      shell: docker stack deploy -c /data/base-docker-compose/product/light/prod/docker-compose.yml {{ artifact.stdout }} || /bin/true 

4、swarm集群部署compose.yml

# cat docker-compose.yml 
version: “3”
services:
  config-server:
    image: registry.sreop.com:5000/leradio/light:1.0.1-RELEASE
    command: java -Djava.security.egd=file:/dev/./urandom -jar light-1.0.1-RELEASE.jar –spring.cloud.config.profile=prod –spring.profiles.active=prod
    ports:
      – 12429:12429
    deploy:
      replicas: 2
      update_config:
        parallelism: 2
      restart_policy:
        condition: on-failure
networks:
  frontend:

初步体验docker swarm集群

之前用的阿里云容器服务,但由于acsrouting的路由错乱问题,被逼上自建docker swarm的梁山。今天尝试自己搭建docker swarm,竟然轻松搞定,简单的超乎想象。

以下是实际搭建操作步骤:

1、创建集群

# docker swarm init --advertise-addr 10.251.242.231
Swarm initialized: current node (m9dfl7r9wo1e9jxsp3oe5du3x) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token xxx 10.251.242.231:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

主:10.251.242.231 是 docker 主机的内网IP地址

2、docker info 查看刚刚创建的集群信息

Swarm: active
 NodeID: m9dfl7r9wo1e9jxsp3oe5du3x
 Is Manager: true
 ClusterID: j01wzizw7gy0ck95p1d7a4pmv
 Managers: 1
 Nodes: 1
 Orchestration:
  Task History Retention Limit: 5

3、docker node ls 查看节点信息

ID                            HOSTNAME               STATUS              AVAILABILITY        MANAGER STATUS
m9dfl7r9wo1e9jxsp3oe5du3t *   swarm-websites-node1   Ready               Active              Leader

4、创建用于部署应用容器的网络

# docker network create --driver=overlay --attachable cnblogs

5、创建用于部署Docker Flow Proxy容器的网络(Docker Flow Proxy支持根据主机名将请求转发至对应的应用容器)

docker network create --driver overlay proxy

6、查看刚刚创建的网络

# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
rl96kcw8ytpa        cnblogs             overlay             swarm
61kbxtvnivkx        proxy               overlay             swarm

7、部署 Docker Flow Proxy stack

7.1 下载 docker-compose-stack.yml 文件

# curl -o docker-compose-stack.yml 
    https://raw.githubusercontent.com/
vfarcic/docker-flow-proxy/master/docker-compose-stack.yml

7.2 部署 stack

# docker stack deploy -c docker-compose-stack.yml proxy

8、编写示例应用 openapi 的 docker-compose-stack.yml

version: '3.2'
services:
  api:
    image: open-api:latest
    deploy:
      replicas: 2
      update_config:
        delay: 5s
      labels:
        - com.df.notify=true
        - com.df.distribute=true
        - com.df.serviceDomain=api.cnblogs.com
        - com.df.port=80
    networks:
      - cnblogs
      - proxy
networks:
  cnblogs:
    external: true
  proxy:
    external: true

9、部署示例应用的 stack

docker stack deploy -c docker-compose-stack.yml openapi

10、部署完成后可以通过 http://api.cnblogs.com 访问运行在 openapi_api service 中的站点

11、添加机器进集群作为节点

docker swarm join --token xxxxxxxxx IP:2377

12、将节点提升为manager

docker node promote ID或HOSTNAME

Centos7安装配置Python3.6 Django virtualenv gunicorn supervisor环境

跟着网上的教程走发现行不通阿!好多都是写个大概,而且每人的环境都是有些许差异的,比如说权限问题阿,等等都会造成安装的失败

说明:本教程在你已经拥有Centos7系统,已经安装好nginx服务器,已经安装了Python3.6 Django virtualenv gunicorn supervisor的前提下进行

接下来开始了!

1、新建你的django项目,假设项目名为Hello

 django-admin.py startproject Hello

2、想好你需要的端口号,假设端口号为8001(下面的端口号均以8001来举例,你可以换成你所需要的端口号),接下来启动服务器看看能不能运行,分两种情况

2.1 如果你只是想在本地运行则

python manage.py runserver127.0.0.1:8001

2.2 如果你想要外网也可以访问则

python manage.py runserver0.0.0.0:8001

3、接下来在浏览器中输入 “服务器ip:8001” ,比如我服务器的公网IP为 192.163.189.166 则输入192.163.189.166:8001,可能会出现三种情况!

3.1 成功运行

3.2 出现 DisallowedHost at / Invalid HTTP_HOST header: ‘10.211.55.6:8001’. You may need to add u’10.211.55.6′ to ALLOWED_HOSTS. 类似错误,解决方法:
进入项目目录下的Hello目录(注意项目目录名是和该名称相同的,此Hello和manage.py同级打开setting.py将ALLOWED_HOSTS = []改为ALLOWED_HOSTS = [‘*’]

3.3 如果在确保地址输入正确,端口也正确的前提下浏览器出现了 Unable to connect 错误,那么很可能是你的Centos7没有开启8001端口号的原因,解决方法

开启端口号

firewall-cmd --zone=public --add-port=8001/tcp --permanent (--permanent意思是永久生效,重启后继续生效)

重启防火墙

firewall-cmd --reload

此时再访问浏览器,如果还是访问不了,那可能是我没遇到的情况,还请自行搜索解决哦

4、配置virtualenv gunicorn

4.1 在项目根目录下输入指令 virtualenv venv (venv可以是其他名字了)

4.2 虚拟环境生成后接着要在虚拟环境中安装django 和 gunicorn 了

pip install django
pip install gunicorn

4.3 在项目根目录下创建gunicorn.conf 用来配置gunicorn,我的配置为

workers = 4
bind = '0.0.0.0:8088'

5、配置supervisor

supervisor的配置文件一般在/etc/supervisord.conf

5.1 vim /etc/supervisord.conf

5.2 在末尾加入

[program:hello]
command=/项目路径/venv/bin/gunicorn -c /项目路径/gunicorn.conf Hello.wsgi:application
directory=/项目路径
autostart=true
autorestart=true
stdout_logfile=/项目路径/logs/gunicorn.log
stderr_logfile=/项目路径/logs/gunicorn.err  

5.3 重启 supervisor

unlink /tmp/supervisor.sock
supervisord -c /etc/supervisord.conf

6、配置nignx

6.1 打开nignx.conf

6.2 在合适地方加入

location /  {
     proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
     proxy_set_header Host $http_host;
     proxy_redirect off;
     proxy_pass http://192.163.189.166:8001;   #http://外网ip:8001,如果是本机访问则
http://127.0.0.1:8001
              }

6.3 重启nginx

systemctl restart nginx

7、好啦,接下来在浏览器中输入 http://192.163.189.166:8001 应该能访问咯。

使用python的pyenv工具管理python virtualenv虚拟环境

pyenv安装

1. 安装依赖包

yum -y install git gcc make patch zlib-devel gdbm-devel openssl-devel sqlite-devel bzip2-devel readline-devel

2. 安装pyenv

curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash

3. 设置环境变量

echo >> .bash_profile << EOF
# pyenv settings
export PATH="~/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
EOF

4. 使之生效

. .bash_profile或者source .bash_profile

这时候pyenv就可以使用了

pyenv使用指南

1、pyenv versions查看系统的上安装的Python版本。

其中前面的*表示当前工作目录正在使用的版本,其中 的 system表示系统自带的 Python 版本:

$ pyenv versions
* system (set by /Users/yulongjun/.pyenv/version)

2、pyenv install 安装其他版本的Python。
例如安装3.6.1和2.7.13版本:

$ pyenv install 3.6.1   # 安装3.6.1版本的Python
$ pyenv install 2.7.13  # 安装2.7.13版本的Python
$ pyenv versions        # 可以看到3个版本,system为系统自带的版本
* system (set by /usr/local/var/pyenv/version)
  2.7.13
  3.6.1
$ cd                   # 到家目录
$ mkdir Python36       # 创建Python3.6的工作目录
$ cd Python36
$ pyenv local 3.6.1    # 使当前工作目录使用Python3.6.1版本
$ python -V            # 查看一下当前目录用Python的版本,确实是3.6.1
Python3.6.1
$ pip -V               # 查看一下pip版本,是3.6的pip
pip 9.0.1 from /usr/local/var/pyenv/versions/3.6.1/lib/python3.6/site-packages 
$ cd                   # 回到家目录
$ mkdir Python27       # 创建python2.7的工作目录
$ cd Python27
$ pyenv local 2.7.13   # 使当前工作目录使用Python2.7.13版本
$ python -V            # 查看一下当前目录用Python的版本,确实是2.7.13
Python 2.7.13
$ pip -V               # 查看一下pip版本,是2.7的pip
pip 9.0.1 from /usr/local/var/pyenv/versions/2.7.13/lib/python2.7/site-packages (python 2.7)

pyenv-virtualenv的使用方法

pyenv-virtualenv是用来创建一个干净的虚拟Python环境的命令,通常在创建干净的新项目时候使用。使用方法如下:

1、创建虚拟环境–pyenv virtualenv 版本号 虚拟环境名。

$ pyenv virtualenv 3.6.1 venv-3.6.1

2、创建项目,让项目使用干净的Python3.6.1的虚拟环境:

[yulongjun@yulongjun ~]$ mkdir Learning-Python3
[yulongjun@yulongjun ~]$ cd Learning-Python3/
[yulongjun@yulongjun Learning-Python3]$ pyenv local venv-3.6.1
(venv-3.6.1) [yulongjun@yulongjun Learning-Python3]$ cd ..
[yulongjun@yulongjun ~]$ cd Learning-Python3/
(venv-3.6.1) [yulongjun@yulongjun Learning-Python3]$

我们会发现:只要我们进入Learning-Python3目录,就会自动激活virtualenv,退出Learning-Python3目录,就会关闭virtualenv。

如果要关闭自动激活,可以运行命令pyenv deactivate,要重新启用的话,运行pyenv activate 虚拟环境名。

ubuntu系统安装virtualenv python版本管理工具

virtualenv的官网在http://www.virtualenv.org/en/latest/
如其官方所说,virtualenv 是一个创建独立python环境的工具。其要解决的最基本问题就是库的依赖和版本,以及间接权限(indirectly permisions,没太明白)。

安装virtualenv,在默认的python2下的pip就行:

sudo apt-get install python-virtualenv

创建虚拟环境:

virtualenv -p /usr/bin/python3 py3env

激活虚拟环境:

source py3env/bin/activate

你会注意到shell的提示符行前多了(py3env)字样,这样你就可以放心的使用python3做开发了。先下载个三方库试试吧

pip install httplib2

大功告成了!

如果要退出python3虚拟环境,输入命令

deactivate

python使用sqlalchemy连接mysql数据库

sqlalchemy是python当中比较出名的orm程序。

什么是orm?

orm英文全称object relational mapping,就是对象映射关系程序,简单来说我们类似python这种面向对象的程序来说一切皆对象,但是我们使用的数据库却都是关系型的,为了保证一致的使用习惯,通过orm将编程语言的对象模型和数据库的关系模型建立映射关系,这样我们在使用编程语言对数据库进行操作的时候可以直接使用编程语言的对象模型进行操作就可以了,而不用直接使用sql语言。

什么是sqlalchemy?

sqlalchemy是python的orm程序,在整个python界当中相当出名。

安装sqlalchemy

在使用sqlalchemy之前要先给python安装mysql驱动,由于我使用的是python3原来的mysqldb不可用,所以这里推荐使用pymysql。
我们通过pip进行安装,在windows下使用pip安装包的时候要记得使用管理员身份运行cmd不然有些操作是无法进行的。

pip install pymysql

安装完以后安装再安装sqlalchemy

pip install sqlalchemy

如何使用sqlalchemy连接mysql?

通过import导入必要的包

from sqlalchemy import create_engine,Table,Column,Integer,String,MetaData,ForeignKey

创建一个连接引擎

engine=create_engine("mysql+pymysql://root:a5230411@localhost:3306/test",echo=True)

我们将连接引擎放到engine里面方便后面使用。
create_engine(“数据库类型+数据库驱动://数据库用户名:数据库密码@IP地址:端口/数据库”,其他参数)
上文当中echo=True是开启调试,这样当我们执行文件的时候会提示相应的文字。

创建元数据

什么是元数据?元数据就是描述数据的数据,举个简单的例子,小明身高170cm,体重50kg,性别男。其中身高,体重,性别就是元数据。当我们创建好连接引擎以后可以通过这个引擎抓取元数据。

metadata=MetaData(engine)

通过MetaData()方法创建了metadata实例,在这个方法里面带上engine的目的是绑定要连接引擎,当我们对这个metadata实例进行操作的时候就会直接连接到数据库。

添加表结构

设定好连接引擎和元数据,让我们向mysql里面创建表结构来进行测试。

user=Table('user',metadata,
    Column('id',Integer,primary_key=True),
    Column('name',String(20)),
    Column('fullname',String(40)),
    )
address_table = Table('address', metadata,
    Column('id', Integer, primary_key=True),
    Column('user_id', None, ForeignKey('user.id')),
    Column('email', String(128), nullable=False)
    )

其中Table()方法用来创建表,第一个参数为表明,第二是存入元数据,后面的参数使用Column()方法将数据库当中每一个字段的数据参数设置好。

执行创建

metadata.create_all()

因为已将将表结构存到了metadata里面,然后让metadata执行create_all()方法,这样就向数据库里创建了user和address表。

完成代码

from sqlalchemy import create_engine,Table,Column,Integer,String,MetaData,ForeignKey
engine=create_engine("mysql+pymysql://root:a5230411@localhost:3306/test",echo=True)
metadata=MetaData(engine)

user=Table('user',metadata,
    Column('id',Integer,primary_key=True),
    Column('name',String(20)),
    Column('fullname',String(40)),
    )
address_table = Table('address', metadata,
    Column('id', Integer, primary_key=True),
    Column('user_id', None, ForeignKey('user.id')),
    Column('email', String(128), nullable=False)
    )

metadata.create_all()

Ubuntu系统使用Nginx uWSGI部署Flask应用

我职业生涯的大部分都在使用微软的架构,最近我决定走出技术的舒适区,步入开源软件世界。我现在日常工作的项目是一个RESTful服务,这个服务需要在主流硬件上运行,且能够按照需要进行水平拓展。为完成这项工作我决定使用Flask和Nginx。Flask是一个轻量级的Python Web框架,Nginx是一个非常稳定的Web服务器,它们在廉价硬件平台上工作良好。

在这篇文章中我将指导你完成使用Nginx服务器托管Flask应用的安装、配置过程。我所使用的操作系统是Ubuntu 13.04。

前提条件

在我们开始安装Nginx及其他所需软件之前先安装一些前提软件。首先,我们需要PIP与virtualenv:

sudo apt-get install python-setuptools
sudo easy_install pip
sudo pip install virtualenv

使用apt-get安装Nginx的话,我们需要添加Nginx库到apt-get source中:

sudo add-apt-repository ppa:nginx/stable

注意:如果“add-apt-repository”命令在你的Ubuntu版本中不存在的话,你需要安装“software-properties-common”包,使用命令:sudo apt-get software-properties-common(感谢get_with_it在评论中提到)

升级已有的包,确保系统上有uWSGI所需的编译器和工具:

sudo apt-get update && sudo apt-get upgrade
sudo apt-get install build-essential python python-dev

Nginx

安装并运行Nginx:

sudo apt-get install nginx
sudo /etc/init.d/nginx start

Nginx是一个提供静态文件访问的web服务,然而,它不能直接执行托管Python应用程序,而uWSGI解决了这个问题。让我们先安装uWSGI,稍候再配置Nginx和uWSGI之间的交互。

sudo pip install uwsgi

里程碑 #1

打开浏览器访问你的服务器,你应该能看到Nginx欢迎页:

示例应用

我们将托管的应用是经典的“Hello, world!”。这个应用只有一个页面,已经猜到页面上将有什么内容了吧。将所有应用相关的文件存放在/var/www/demoapp文件夹中。下面创建这个文件夹并在其中初始化一个虚拟环境:

sudo mkdir /var/www
sudo mkdir /var/www/demoapp

由于我们使用root权限创建了这个文件夹,它目前归root用户所有,让我们更改它的所有权给你登录的用户(我的例子中是ubuntu)

sudo chown -R ubuntu:ubuntu /var/www/demoapp/

创建并激活一个虚拟环境,在其中安装Flask:

cd /var/www/demoapp
virtualenv venv
. venv/bin/activate
pip install flask

使用下面的代码创建hello.py文件:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)

里程碑 #2

让我们执行我们刚创建的脚本:

python hello.py

现在你可以通过浏览器访问你服务器的8080端口,看,应用生效了:

注意:因为80端口已被Nginx使用,这里我使用8080端口。

现在应用是由Flask内置的web服务托管的,对于开发和调试这确实是个不错的工具,但不推荐在生产环境中使用。让我们配置Nginx来挑起这个重担吧。

配置Nginx

首先删除掉Nginx的默认配置文件:

sudo rm /etc/nginx/sites-enabled/default

注意:如果你安装了其他版本的Nginx,默认配置文件可能在/etc/nginx/conf.d文件夹下。

创建一个我们应用使用的新配置文件/var/www/demoapp/demoapp_nginx.conf:

server {
    listen      80;
    server_name localhost;
    charset     utf-8;
    client_max_body_size 75M;

    location / { try_files $uri @yourapplication; }
    location @yourapplication {
        include uwsgi_params;
        uwsgi_pass unix:/var/www/demoapp/demoapp_uwsgi.sock;
    }
}

将刚建立的配置文件使用符号链接到Nginx配置文件文件夹中,重启Nginx:

sudo ln -s /var/www/demoapp/demoapp_nginx.conf /etc/nginx/conf.d/
sudo /etc/init.d/nginx restart

里程碑 #3

访问服务器的公共ip地址,你会看到一个错误:

别担心,这个错误是正常的,它代表Nginx已经使用了我们新创建的配置文件,但在链接到我们的Python应用网关uWSGI时遇到了问题。到uWSGI的链接在Nginx配置文件的第10行定义:

uwsgi_pass unix:/var/www/demoapp/demoapp_uwsgi.sock;

这代表Nginx和uWSGI之间的链接是通过一个socket文件,这个文件位于/var/www/demoapp/demoapp_uwsgi.sock。因为我们还没有配置uWSGI,所以这个文件还不存在,因此Nginx返回“bad gateway”错误,让我们马上修正它吧。

配置uWSGI

创建一个新的uWSGI配置文件/var/www/demoapp/demoapp_uwsgi.ini:

[uwsgi]
#application's base folder
base = /var/www/demoapp

#python module to import
app = hello
module = %(app)

home = %(base)/venv
pythonpath = %(base)

#socket file's location
socket = /var/www/demoapp/%n.sock

#permissions for the socket file
chmod-socket    = 666

#the variable that holds a flask application inside the module imported at line #6
callable = app

#location of log files
logto = /var/log/uwsgi/%n.log

创建一个新文件夹存放uWSGI日志,更改文件夹的所有权:

sudo mkdir -p /var/log/uwsgi
sudo chown -R ubuntu:ubuntu /var/log/uwsgi

里程碑 #4

执行uWSGI,用新创建的配置文件作为参数:

uwsgi --ini /var/www/demoapp/demoapp_uwsgi.ini

接下来访问你的服务器,现在Nginx可以连接到uWSGI进程了:

我们现在基本完成了,唯一剩下的事情是配置uWSGI在后台运行,这是uWSGI Emperor的职责。

uWSGI Emperor

uWSGI Emperor (很拉风的名字,是不?) 负责读取配置文件并且生成uWSGI进程来执行它们。创建一个初始配置来运行emperor – /etc/init/uwsgi.conf:

description "uWSGI"
start on runlevel [2345]
stop on runlevel [06]
respawn

env UWSGI=/usr/local/bin/uwsgi
env LOGTO=/var/log/uwsgi/emperor.log

exec $UWSGI --master --emperor /etc/uwsgi/vassals --die-on-term --uid www-data --gid www-data --logto $LOGTO

最后一行运行uWSGI守护进程并让它到/etc/uwsgi/vassals文件夹查找配置文件。创建这个文件夹,在其中建立一个到链到我们刚创建配置文件的符号链接。

sudo mkdir /etc/uwsgi && sudo mkdir /etc/uwsgi/vassals
sudo ln -s /var/www/demoapp/demoapp_uwsgi.ini /etc/uwsgi/vassals

同时,最后一行说明用来运行守护进程的用户是www-data。为简单起见,将这个用户设置成应用和日志文件夹的所有者。

sudo chown -R www-data:www-data /var/www/demoapp/
sudo chown -R www-data:www-data /var/log/uwsgi/

注意:我们先前安装的Nginx版本使用“www-data”这个用户来运行Nginx,其他Nginx版本的可能使用“Nginx”这个替代用户。

由于Nginx和uWSGI都由同一个用户运行,我们可以在uWSGI配置中添加一个安全提升项。打开uWSGI配置文件,将chmod-socket值由666更改为644:

...
#permissions for the socket file
chmod-socket    = 644

现在我们可以运行uWSGI了:

sudo start uwsgi

最后,Nginx和uWSGI被配置成启动后立即对外提供我们的应用服务。

问题解决

如果出现错误的话,第一个检查的地方是日志文件。Nginx默认将错误信息写到/var/log/nginx/errors.log文件。

我们已经配置了uWSGI emperor将日志写到/var/log/uwsgi/emperor.log。这个文件夹还包含着每个配置应用的单独日志。我们的例子是 – /var/log/uwsgi/demoapp_uwsgi.log。

静态文件

如果你的应用提供静态文件的话,将下面的规则添加到demoapp_nginx.conf文件:

location /static {
    root /var/www/demoapp/;
}

上面配置的结果就是所有在/var/www/demoapp/static文件夹中的文件将由提供Nginx对外服务(谢谢Bastianh指出)

托管多个应用

如果你想在一台服务器上托管多个Flask应用,为每个应用创建一个单独的文件夹,像我们前面所做的一样,创建Nginx及uWSGI配置文件到应用文件夹的符号链接。

使用Distribute部署应用

使用distribute部署Flask应用的话,首先,按照Flask文档里的步骤将应用转化成package,然后复制distribute通用安装包到服务器上,使用虚拟环境中的Python来安装它。如下:

python setup.py install

最后且同样重要的是,uwsgi配置里应用属性的值要设置成包含Flask应用的包的名称。

supervisor的安装配置及常用命令介绍

前言

在 web 应用部署到线上后,需要保证应用一直处于运行状态,在遇到程序异常、报错等情况,导致 web 应用终止时,需要保证程序可以立刻重启,继续提供服务。

所以,就需要一个工具,时刻监控 web 应用的运行情况,管理该进程。

Supervisor 就是解决这种需求的工具,可以保证程序崩溃后,重新把程序启动起来等功能。

简介

Supervisor 是一个用 Python 写的进程管理工具,可以很方便的用来在 UNIX-like 系统(不支持 Windows)下启动、重启(自动重启程序)、关闭进程(不仅仅是 Python 进程)。

安装

  1. Ubuntu系统下:apt-get install supervisor,通过这种方式安装后,自动设置为开机启动
  2. 也可以通过 pip install supervisor 进行安装,但是需要手动启动,然后设置为开机启动(不推荐这种安装方式)

Supervisor 配置

Supervisor 是一个 C/S 模型的程序,supervisord 是 server 端,supervisorctl 是 client 端。

supervisord

下面介绍 supervisord 配置方法。supervisord 的配置文件默认位于 /etc/supervisord.conf,内容如下(;后面为注释):

; supervisor config file

[unix_http_server]
file=/var/run/supervisor.sock   ; (the path to the socket file) UNIX socket 文件,supervisorctl 会使用
chmod=0700                       ; sockef file mode (default 0700) socket 文件的 mode,默认是 0700

[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log) 日志文件,默认是 $CWD/supervisord.log
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) pid 文件
childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket 通过 UNIX socket 连接 supervisord,路径与 unix_http_server 部分的 file 一致

; 在增添需要管理的进程的配置文件时,推荐写到 `/etc/supervisor/conf.d/` 目录下,所以 `include` 项,就需要像如下配置。
; 包含其他的配置文件
[include]
files = /etc/supervisor/conf.d/*.conf ; 引入 `/etc/supervisor/conf.d/` 下的 `.conf` 文件

program 配置

program 的配置文件就写在,supervisord 配置中 include 项的路径下:/etc/supervisor/conf.d/,然后 program 的配置文件命名规则推荐:app_name.conf

[program:app] ; 程序名称,在 supervisorctl 中通过这个值来对程序进行一系列的操作
autorestart=True      ; 程序异常退出后自动重启
autostart=True        ; 在 supervisord 启动的时候也自动启动
redirect_stderr=True  ; 把 stderr 重定向到 stdout,默认 false
environment=PATH="/home/app_env/bin"  ; 可以通过 environment 来添加需要的环境变量,一种常见的用法是使用指定的 virtualenv 环境
command=python server.py  ; 启动命令,与手动在命令行启动的命令是一样的
user=ubuntu           ; 用哪个用户启动
directory=/home/app/  ; 程序的启动目录

需要注意:

  • 用 supervisord 管理时,gunicorn 的 daemon 选项需要设置为 False
  • 如果启动命令需要包含workon,修改environment参数:environment=PATH=”/home/username/.virtualenvs/myproject/bin”

supervisorctl 操作

supervisorctl 是 supervisord 的命令行客户端工具,使用的配置和 supervisord 一样,这里就不再说了。下面,主要介绍 supervisorctl 操作的常用命令:

输入命令 supervisorctl 进入 supervisorctl 的 shell 交互界面(还是纯命令行)

Linux上的后台进程管理工具Supervisor

当你的系统上有许多工作进程在跑,你想要一个统一的入口来管理这些进程,包括状态检查,启动和关闭,出错时警告,及自动重启等。那你就需要一个进程管理工具来帮助你。Supervisor就是其中一个简单而又强大的工具。虽说标题写了Linux,其实它可以用在大部分Unix衍生出来的系统,比如Linux, Mac OS X, Solaris和FreeBSD。

一. Supervisor组件

Supervisor有四个组件:

1. supervisord

运行Supervisor的后台服务,它用来启动和管理那些你需要Supervisor管理的子进程,响应客户端发来的请求,重启意外退出的子进程,将子进程的stdout和stderr写入日志,响应事件等。它是Supervisor最核心的部分。

2. supervisorctl

相当于supervisord的客户端,它是一个命令行工具,用户可以通过它向supervisord服务发指令,比如查看子进程状态,启动或关闭子进程。它可以连接不同的supervisord服务,包括远程机上的服务。

3. Web服务器

这是supervisord的Web客户端,用户可以在Web页面上完成类似于supervisorctl的功能。

4. XML-RPC接口

这是留给第三方集成的接口,你的服务可以在远程调用这些XML-RPC接口来控制supervisord管理的子进程。上面的Web服务器其实也是通过这个XML-RPC接口实现的。

二. 安装

Supervisor是由Python写的,源码托管在Github仓库里。如果你机上有Python环境,你可以clone一份源码,然后通过setup.py来安装:

$ python setup.py install

也可以通过PyPI安装,这样方便许多:

$ pip install supervisor

另外,Supervisor在Ubuntu和CentOS上都有分发包,比如在Ubuntu上,你就可以用”apt-get”来安装:

$ apt-get install supervisor

注意,根据你系统权限的情况,以上这些命令可能会需要root权限来执行。

三. 配置

安装完毕后,你可以执行下命令”echo_supervisord_conf”,它会将一个配置样例打印在控制台上,如果你看到配置信息了,说明安装成功。现在让我们将配置样例保存在本地:

$ echo_supervisord_conf > supervisord.conf

官网上建议直接保存在”/etc/supervisord.conf”文件中,可是大部分情况下我们是没有权限写etc目录的,所以要先保存下来,再通过root拷贝过去。Supervisor启动时会自动加载该配置文件。

让我们打开配置文件看看,这就是一个INI格式的配置文件啊,参数好多,而且大部分都注视掉了。这里就不一一介绍了,官方文档里有详细的。我们就介绍下最重要的部分,就是”[program:theprogramname]”。

这个”[program:xxx]”块定义了你要Supervisor管理的子进程,冒号后面是你可以起的名字。建议采用比较有意义的名字,因为”[program:xxx]”块可以有多个,这样可以同时管理多个不同的子进程,名字起的好方便区分。让我们了解下这个配置块下几个主要的配置参数吧:

  • command
    子进程的运行命令,比如你要监控一个Python程序”app.py”的运行,你可以设置”command=python /home/bjhee/myapp/app.py”。当supervisord服务启动时,该程序也会被自动启动,并作为子进程由supervisord管理。

  • numprocs
    同时启动的进程个数,用来实现并发,默认是1。注意如果该参数大于1的话,你必须同时配置”process_name”参数,并且将”%(process_num)s”变量放入”process_name”中,防止多个进程同名导致启动出错。

  • directory
    如果配置了这个目录,那子进程运行前,会先切换到这个目录。

  • user
    运行该子进程的用户,默认同supervisord服务的启动用户。如果supervisord由root启动,而你又不想给子进程root,你可以配置这个参数。

  • priority
    该子进程优先级,决定了启动和关闭子进程的顺序,默认是最大值999。

  • autostart
    启动supervisord时,子进程是否自动启动,默认是true。

  • autorestart
    当子进程出错退出时,supervisord是否自动将其重启,默认是unexpected,也就是不自动重启,你可以设为true。

  • stdout-logfile
    由于子进程由supervisord启动,所以其stdout将无法输出到系统的标准输出上,所以你要将子进程的stdout写入到日志文件中。这个参数指定了该日志文件的位置。

  • stderr-logfile
    同上面的stdout-logfile,这里指定了stderr写入的日志文件位置。

四. 启动Supervisor

我们就写个最简单的Web应用吧,可以从我的这篇文章里拷一个Flask的Hello World程序,假设保存在”/home/billy/myapp/app.py”文件中。现在让我们在配置文件中,加入配置项:

[program:app]
command=python /home/billy/myapp/app.py

就这一个配置够了,然后让我们启动supervisord:

$ supervisord

注意,你可能要root来运行supervisord命令。运行该命令时,supervisord会自动在以下几个位置寻找配置文件:

  • 当前目录下的supervisord.conf
  • 当前目录下的etc/supervisord.conf
  • /etc/supervisord.conf
  • /etc/supervisor/supervisord.conf

当你的配置文件不在上述位置时,那就必须指定配置文件的位置,这时启动命令应改为:

$ supervisord -c /home/billy/supervisor/hello.conf

如果你的确想把配置文件放在自己的工作目录下,一个推荐的方式,是依然创建”/etc/supervisord.conf”文件,并在该文件的最后加上”[include]”配置块:

[include]
files = /home/billy/supervisor/*.conf

运行完supervisord命令后,你可以打开supervisord的日志文件,默认是”/tmp/supervisord.log”,你也可以通过”[supervisord]”配置块下的参数”logfile”来指定。如果在日志文件中看到下面的信息,就说明Supervisor启动成功了。

2016-11-27 15:03:06,830 INFO RPC interface 'supervisor' initialized
2016-11-27 15:03:06,830 CRIT Server 'unix_http_server' running without any HTTP     authentication checking
2016-11-27 15:03:06,832 INFO daemonizing the supervisord process
2016-11-27 15:03:06,833 INFO supervisord started with pid 73469

五. 命令行客户端

让我们再打开一个控制台窗口,运行supervisorctl命令,supervisorctl同supervisord一样会自动寻找”supervisord.conf”配置文件,如果不在默认位置的话,启动时须用参数”-c”指定。

supervisorctl启动后,你将会看到一个类似Shell的交互窗口。在这个窗口中,你可以输入”status”命令查看所有子进程的状态;可以输入”stop app”或”start app”命令来关闭或启动名称为”app”的子进程;如果此时你修改了配置文件,你可以输入”reload”命令让supervisord服务重新加载配置文件。

supervisor> status
app                              RUNNING   pid 74652, uptime 0:00:34
supervisor> stop app
app: stopped
supervisor> status app
app                              STOPPED   Nov 27 10:35 PM

当你不清楚命令怎么用时,可以输入”help”查看帮助,或者类似于”help start”来查看”start”命令的帮助。

supervisor> help start
start <name>        Start a process
start <gname>:*     Start all processes in a group
start <name> <name> Start multiple processes or groups
start all       Start all processes

六. Web客户端

使用Web客户端前,你先要在配置文件中启用它,方法是添加下列配置项:

[inet_http_server]
port=*:9001
;username=user
;password=123

用户名密码是用于安全验证,不设的话任何人都可以访问。配置完毕启动supervisord服务,你就可以通过”http://localhost:9001″来访问Web客户端了。下面是页面的示例,可以看到这里允许你从Web页面上查看/启动/关闭子进程。

未分类

七. 进程组

当你要管理的子进程非常多时,Supervisor允许你将子进程分组,也就是多个子进程可以在同一组内统一管理。你只需要配置”[group:thegroupname]”块就行了。

我们可以试下,再配置一个”[program:hello]”子程序,然后加上”[group:xxx]”配置:

[program:hello]
command=python /home/billy/myapp/hello.py

[group:appgroup]
programs=app,hello

现在我们将子进程”app”和”hello”都配置在进程组”appgroup”中,启动supervisord,让我们在supervisorctl客户端下试一试:

supervisor> status appgroup:*
appgroup:app                     RUNNING   pid 75042, uptime 0:03:12
appgroup:hello                   RUNNING   pid 75043, uptime 0:03:12
supervisor> stop appgroup:*
appgroup:app: stopped
appgroup:hello: stopped
supervisor> status appgroup:*
appgroup:app                     STOPPED   Nov 27 11:05 PM
appgroup:hello                   STOPPED   Nov 27 11:05 PM

子进程名前都加了前缀”appgroup:”,这样我们使用通配符”appgroup:*”来执行的命令就对进程组内所有的子进程起效了,果然很方便。