Docker Swarm 下搭建 MongoDB 分片+副本+选举集群

一、环境准备

三台服务器,建立 Docker Swarm 集群,一个 Manager,两个 Worker。

  • docker 版本:17-09
  • mongo 版本:3.6

二、MongoDB 集群架构设计

未分类

高清图地址: https://www.processon.com/view/link/5a3c7386e4b0bf89b8530376

三、搭建集群

1、【Manager】创建集群网络

docker network create -d overlay --attachable mongo

–attachable 允许其他容器加入此网络

2、创建 9 个 Data 服务,3 个 Config 服务,1 个 Global 模式的 Mongos 服务

2.1、【所有机器】创建相关文件夹

mkdir /root/mongo/config /root/mongo/shard1 /root/mongo/shard2 /root/mongo/shard3

2.2、【Manager】创建 stack.yml

version: '3.3'
services:
  mongors1n1:
    # docker 中国的镜像加速地址
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard1 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard1:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        # 指定在服务器 manager 上启动
        constraints:
          - node.hostname==manager
  mongors2n1:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard2 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard2:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==manager
  mongors3n1:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard3 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard3:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==manager
  mongors1n2:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard1 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard1:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker1
  mongors2n2:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard2 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard2:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker1
  mongors3n2:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard3 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard3:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker1
  mongors1n3:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard1 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard1:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker2
  mongors2n3:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard2 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard2:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker2
  mongors3n3:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard3 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard3:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker2
  cfg1:
    image: registry.docker-cn.com/library/mongo
    command: mongod --configsvr --replSet cfgrs --smallfiles --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/config:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==manager
  cfg2:
    image: registry.docker-cn.com/library/mongo
    command: mongod --configsvr --replSet cfgrs --smallfiles --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/config:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker1
  cfg3:
    image: registry.docker-cn.com/library/mongo
    command: mongod --configsvr --replSet cfgrs --smallfiles --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/config:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker2
  mongos:
    image: registry.docker-cn.com/library/mongo
    # mongo 3.6 版默认绑定IP为 127.0.0.1,此处绑定 0.0.0.0 是允许其他容器或主机可以访问
    command: mongos --configdb cfgrs/cfg1:27017,cfg2:27017,cfg3:27017 --bind_ip 0.0.0.0 --port 27017
    networks:
      - mongo
    # 映射宿主机的 27017 端口
    ports:
      - 27017:27017
    volumes:
      - /etc/localtime:/etc/localtime
    depends_on:
      - cfg1
      - cfg2
      - cfg3
    deploy:
      restart_policy:
        condition: on-failure
      # 在集群内的每一台服务器上都启动一个容器
      mode: global
networks:
  mongo:
    external: true

2.3、启动服务,在 Manager 上执行

docker stack deploy -c stack.yml mongo

2.4、【Manager】查看服务的启动情况

docker service ls

正常情况下,会出现如下结果:

[docker@manager ~]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                                         PORTS
z1l5zlghlfbi        mongo_cfg1          replicated          1/1                 registry.docker-cn.com/library/mongo:latest
lg9vbods29th        mongo_cfg2          replicated          1/1                 registry.docker-cn.com/library/mongo:latest
i6d6zwxsq0ss        mongo_cfg3          replicated          1/1                 registry.docker-cn.com/library/mongo:latest
o0lfdavd8kpj        mongo_mongors1n1    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
n85yeyod7mlu        mongo_mongors1n2    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
cwurdqng9tdk        mongo_mongors1n3    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
vu6al5kys28u        mongo_mongors2n1    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
xrjiep0vrf0w        mongo_mongors2n2    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
qqzifwcejjyk        mongo_mongors2n3    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
tddgw8hygv1b        mongo_mongors3n1    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
qrb6fjty03mw        mongo_mongors3n2    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
m8ikdzjssmhn        mongo_mongors3n3    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
mnnlm49b7kyb        mongo_mongos        global              3/3                 registry.docker-cn.com/library/mongo:latest   *:27017->27017/tcp

3、初始化集群

3.1 【Manager】初始化 Mongo 配置集群

docker exec -it $(docker ps | grep "cfg1" | awk '{ print $1 }') bash -c "echo 'rs.initiate({_id: "cfgrs",configsvr: true, members: [{ _id : 0, host : "cfg1" },{ _id : 1, host : "cfg2" }, { _id : 2, host : "cfg3" }]})' | mongo"

3.2 【Manager】初始化三个 Mongo 数据集群

docker exec -it $(docker ps | grep "mongors1n1" | awk '{ print $1 }') bash -c "echo 'rs.initiate({_id : "shard1", members: [{ _id : 0, host : "mongors1n1" },{ _id : 1, host : "mongors1n2" },{ _id : 2, host : "mongors1n3", arbiterOnly: true }]})' | mongo"

docker exec -it $(docker ps | grep "mongors2n1" | awk '{ print $1 }') bash -c "echo 'rs.initiate({_id : "shard2", members: [{ _id : 0, host : "mongors2n1" },{ _id : 1, host : "mongors2n2" },{ _id : 2, host : "mongors2n3", arbiterOnly: true }]})' | mongo"

docker exec -it $(docker ps | grep "mongors3n1" | awk '{ print $1 }') bash -c "echo 'rs.initiate({_id : "shard3", members: [{ _id : 0, host : "mongors3n1" },{ _id : 1, host : "mongors3n2" },{ _id : 2, host : "mongors3n3", arbiterOnly: true }]})' | mongo"

3.3 【Manager】将三个数据集群当做分片加入 mongos

docker exec -it $(docker ps | grep "mongos" | awk '{ print $1 }') bash -c "echo 'sh.addShard("shard1/mongors1n1:27017,mongors1n2:27017,mongors1n3:27017")' | mongo "

docker exec -it $(docker ps | grep "mongos" | awk '{ print $1 }') bash -c "echo 'sh.addShard("shard2/mongors2n1:27017,mongors2n3:27017,mongors2n3:27017")' | mongo "

docker exec -it $(docker ps | grep "mongos" | awk '{ print $1 }') bash -c "echo 'sh.addShard("shard3/mongors3n1:27017,mongors3n2:27017,mongors3n3:27017")' | mongo "

4、连接集群

4.1 内部:在 mongo 网络下的容器,通过 mongos:27017 连接

4.2 外部:通过 IP:27017 连接,IP 可以为三台服务的中的一个的 IP

sqlalchemy触发器的使用-Event

说是触发器,其实并不是触发器,这是sqlalchemy中的钩子,也称为事件,在触发某个操作的时候执行某个函数,和sql中的触发器时一样的,更加灵活简单。

我现在也正在学习,我就直接拿出来一个例子吧,大家可以测试一下。

#coding:utf8

from sqlalchemy.orm import scoped_session
from sqlalchemy import Column, Integer, String, DateTime, TIMESTAMP, DECIMAL, func, Text, or_
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import ForeignKey, Boolean, create_engine, MetaData, Constraint
from sqlalchemy.orm import relationship, backref, sessionmaker
from sqlalchemy import event


Base = declarative_base()




class Role(Base):#  一
    __tablename__= 'roles'
    id = Column(Integer,primary_key=True)
    name = Column(String(36),nullable=True)
    users = relationship('User',backref='role')

class User(Base):#  多
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True)
    name = Column(String(36),nullable=True)
    role_id = Column(Integer, ForeignKey('roles.id'))


class Database():
    def __init__(self, bind, pool_size=100, pool_recycle=3600, echo=False):
        self.__engine = create_engine(bind,pool_size=pool_size,
                                   pool_recycle=pool_recycle,
                                   echo=echo)
        self.__session_factory = sessionmaker(bind=self.__engine)
        self.__db_session = scoped_session(self.__session_factory)
        Base.metadata.create_all(self.__engine)

    @property
    def session(self):
        return self.__db_session()


def on_created(target, value, initiator):
    print "received append event for target: %s" % target



@event.listens_for(User, 'after_insert')
def receive_after_insert(mapper, connection, target):
    print mapper
    print connection
    print target.id
    print "insert......."

db = Database(bind="mysql+pymysql://root:xxxx@localhost/mydata?charset=utf8")





if __name__ == "__main__":
    user = User()
    user.name = "123"
    user.role_id=2
    db.session.add(user)
    db.session.commit()

在插入数据后会执行receive_after_insert函数,很简单。

如果想深入的学习建议看官方文档,说的很详细
http://docs.sqlalchemy.org/en/latest/orm/events.html#attribute-events

flask如何处理并发

1、使用自身服务器的多进程或者多线程,参考werkzeug的run_simple函数的入参。注意,进程和线程不能同时开启

2、使用gunicorn使用多进程,-w worker 进程数,类型于运行多个app.run()开发服务器

gunicorn app -w 2 -b :8000

3、使用gevent异步

/usr/local/bin/gunicorn -t120 -w10  -b 10.57.17.57:3000 --worker-class gevent  Erebus:APP
-k STRING, --worker-class STRING
                        The type of workers to use. [sync]

-w INT, --workers INT
                        The number of worker processes for handling requests.
                        [1]

-t INT, --timeout INT
                        Workers silent for more than this many seconds are
                        killed and restarted. [30]

-b ADDRESS, --bind ADDRESS
                        The socket to bind. [['127.0.0.1:8000']]

当运行开发服务器时,运行app.run(),你会得到一个单一的同步进程,这意味着一次最多只能处理1个请求。

通过在其默认配置中坚持Gunicorn在它的前面,只是增加 – 工作,你获得的本质上是一些进程(由Gunicorn管理),每个行为像app.run()开发服务器。 4个worker == 4个并发请求。这是因为Gunicorn默认使用它包含的同步工作类型。

重要的是要注意,Gunicorn还包括异步工作,即eventlet和gevent(和龙卷风,但是最好使用Tornado框架,似乎)。通过使用–worker-class标志指定其中一个异步工作者,您所获得的是Gunicorn管理多个异步进程,每个进程管理自己的并发。这些进程不使用线程,而是协同程序。基本上,在每个进程内,每次只能发生一件事情(1个线程),但是当对象在等待外部进程完成时(可以考虑数据库查询或等待网络I / O),它们可以被“暂停”。

这意味着,如果你使用Gunicorn的异步工作者,每个工作者可以一次处理多个请求。只有多少工人是最好的取决于你的应用程序的性质,它的环境,它运行的硬件等等。更多的细节可以在Gunicorn’s design页和notes on how gevent works在其介绍页上找到。

使用Nginx 和Supervisor在Linux服务器上部署Tornado

Nginx 安装: sudo apt-get install nginx

Nginx 安装后用浏览器进入127.0.0.1就可以看到nginx的欢迎页了

nginx 常用命令

  1. sudo service nginx start 启动nginx
  2. sudo service nginx stop 停止nginx
  3. sudo service nginx restart 重启nginx
  4. sudo service nginx reload 重新加载配置文件

未分类

Supervisor 安装: sudo apt-get install supervisor

部署步骤:

  • Tornado项目路径 : /home/你的用户名/Tornado项目文件夹名称/main.py

  • 在/etc/nginx/下 创建nginx.conf配置文件
    这里我们使用8000-8003四个端口,进行端口转发 配置文件编写要注意main.py所在位置要写对,即下面配置文件中的中文

user root;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 768;
    multi_accept on;
    use epoll;
}
http {
    # Enumerate all the Tornado servers here
    upstream frontends {
        server 127.0.0.1:8000;
        server 127.0.0.1:8001;
        server 127.0.0.1:8002;
        server 127.0.0.1:8003;
    }

    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    access_log /var/log/nginx/access.log;

    keepalive_timeout 65;
    proxy_read_timeout 200;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    gzip on;
    gzip_min_length 1000;
    gzip_proxied any;
    gzip_types text/plain text/html text/css text/xml
               application/x-javascript application/xml
               application/atom+xml text/javascript;
    proxy_next_upstream error;

    server {
        listen 80;

        # Allow file uploads
        client_max_body_size 50M;

        location ^~ /static/ {
            root /home/用户名/项目文件夹名/;
            if ($query_string) {
                expires max;
            }
        }
        location = /favicon.ico {
            rewrite (.*) /static/favicon.ico;
        }
        location = /robots.txt {
            rewrite (.*) /static/robots.txt;
        }

        location / {
            proxy_pass_header Server;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Scheme $scheme;
            proxy_pass http://frontends;
        }
    }
}
  • 编写supervisor配置文件

  • 进入supervisor配置文件夹 cd /etc/supervisor/conf.d/

  • 创建tornados.conf

[group:tornadoes]
programs=tornado-8000,tornado-8001,tornado-8002,tornado-8003

[program:tornado-8000]
command=python /home/用户名/项目文件夹名/main.py --port=8000
directory=/home/用户名/项目文件夹名
user=root
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/tornado.log
loglevel=info

[program:tornado-8001]
command=python /home/用户名/项目文件夹名/main.py --port=8001
directory=/home/用户名/项目文件夹名
user=root
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/tornado.log
loglevel=info

[program:tornado-8002]
command=python /home/用户名/项目文件夹名/main.py --port=8002
directory=/home/用户名/项目文件夹名
user=root
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/tornado.log
loglevel=info

[program:tornado-8003]
command=python /home/用户名/项目文件夹名/main.py --port=8003
directory=/home/用户名/项目文件夹名
user=root
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/tornado.log
loglevel=info
  • 其中/var/log/tornado.log为日志文件目录
    然后先重载nginx的配置文件 sudo service nginx reload
    重启 nginx : sudo service nginx restart
    supervisor开启所有进程: sudo supervisorctrl restart all

再次打开127.0.0.1后可以看到项目已经成功部署。

Linux下的守护进程工具-Supervisor

简介:没有

安装

sudo apt-get install supervisor

(其实我是用Mint的软件管理器安装的……)

配置

参考帖子:

详解supervisor使用教程 http://www.jb51.net/article/128676.htm

supervisor 从安装到使用 http://www.jianshu.com/p/3658c963d28b

使用 Supervisor 进行进程管理 http://www.jianshu.com/p/2a48b2c987e0

执行命令

sudo supervisord
sudo supervisorctl start/stop/restart RROGRAM_NAME
sudo supervisorctl reload
sudo supervisorctl status

配置文件模板

我的配置文件在/etc/supervisor/supervisord.conf
我的配置模板:

[program:ssr]
command = python local.py -c /home/jerry/opt/shadowsocksr-manyuser/shadowsocks/shadowsocksr.json
stdout_logfile=/tmp/ssr.log
stdout_logfile_backups=2 ;默认为10 
stdout_capture_maxbytes=1MB 

[program:drcom]
command = python2 /home/jerry/opt/DRCOM_PY/latest-wired.py
stdout_logfile=/tmp/drcom.log
stdout_logfile_backups=2 ;默认为10 
stdout_capture_maxbytes=1MB 
autostart= false

通用模板

;[program:theprogramname]
;command=/bin/cat              ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1                    ; number of processes copies to start (def 1)
;directory=/tmp                ; directory to cwd to before exec (def no cwd)
;umask=022                     ; umask for process (default None)
;priority=999                  ; the relative start priority (default 999)
;autostart=true                ; start at supervisord start (default: true)
;startsecs=1                   ; # of secs prog must stay up to be running (def. 1)
;startretries=3                ; max # of serial start failures when starting (default 3)
;autorestart=unexpected        ; when to restart if exited after running (def: unexpected)
;exitcodes=0,2                 ; 'expected' exit codes used with autorestart (default 0,2)
;stopsignal=QUIT               ; signal used to kill process (default TERM)
;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false             ; send stop signal to the UNIX process group (default false)
;killasgroup=false             ; SIGKILL the UNIX process group (def false)
;user=chrism                   ; setuid to this UNIX account to run the program
;redirect_stderr=true          ; redirect proc stderr to stdout (default false)
;stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10     ; # of stdout logfile backups (default 10)
;stdout_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
;stdout_events_enabled=false   ; emit events on stdout writes (default false)
;stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10     ; # of stderr logfile backups (default 10)
;stderr_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)
;stderr_events_enabled=false   ; emit events on stderr writes (default false)
;environment=A="1",B="2"       ; process environment additions (def no adds)
;serverurl=AUTO                ; override serverurl computation (childutils)

; The below sample eventlistener section shows all possible
; eventlistener subsection values, create one or more 'real'
; eventlistener: sections to be able to handle event notifications
; sent by supervisor.

进程管理工具Supervisor(二)Events

supervisor可以当做一个简单的进程启动、重启、控制工具使用,也可以作为一个进程监控框架使用,作为后者,需要使用supervisor的Events机制。

Event Listeners

supervisor对子程序的监控通过叫做event listener的程序实现。supervisor控制的子程序状态发生变化时,就会产生一些事件通知,event listener可以对这些事件通知进行订阅。

event listener本身也是作为supervisor的子程序运行的。事件通知协议的实现基于event listener子程序的stdin和stdout。supervisor发送特定格式的信息到event listener的stdin,然后从event listener的stdout获得特定格式的输出,从而形成一个请求/应答循环。

配置

event listener的配置放置于配置文件中的[eventlistener:x]块中。

[eventlistener:mylistener]
command=my_custom_listener.py
events=PROCESS_STATE,TICK_60

x是listener的名称,command是执行listener脚本的命令,events是要监控的事件类型。

event listener本身是作为supervisor的子程序运行的,所以与配置子程序[program:x]块类似,官网例子:

[eventlistener:theeventlistenername]
command=/bin/eventlistener
process_name=%(program_name)s_%(process_num)02d
numprocs=5
events=PROCESS_STATE
buffer_size=10
directory=/tmp
umask=022
priority=-1
autostart=true
autorestart=unexpected
startsecs=1
startretries=3
exitcodes=0,2
stopsignal=QUIT
stopwaitsecs=10
stopasgroup=false
killasgroup=false
user=chrism
redirect_stderr=false
stdout_logfile=/a/path
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=10
stdout_events_enabled=false
stderr_logfile=/a/path
stderr_logfile_maxbytes=1MB
stderr_logfile_backups=10
stderr_events_enabled=false
environment=A="1",B="2"
serverurl=AUTO

事件通知协议

一个event listener可以处于三种状态,ACKNOWLEDGED、READY、BUSY,只有在READY状态下才可以接收事件通知。

event listener启动时处于ACKNOWLEDGED状态,直到event listener向stdout中输出“READYn”字符串为止。

event listener向stdout中输出“READYn”之后就处于READY状态,supervisor会向处于READY状态的listener发送listener订阅的事件通知。

listener接收事件通知之后就处于BUSY状态,期间listener对接收到的事件通知进行处理,处理结束后向stdout输出“RESULT 2nOK”或者“RESULT 4nFAIL”,前者代表处理成功,后者代表处理失败。

supervisor收到OK或者FAIL输出后,就将event listener的状态置于ACKNOWLEDGED。FAIL的事件通知会被缓存然后再次发送。

event listener的状态处于ACKNOWLEDGED后可以退出执行,也可以继续执行,继续执行就可以向stdout输出“READYn”形成一个循环。

supervisor向listener发送的事件通知由两部分组成,header和body,由”n”换行符分开。

一个header例子:

ver:3.0 server:supervisor serial:21 pool:listener poolserial:10 eventname:PROCESS_COMMUNICATION_STDOUT len:54

ver:协议版本

server:supervisor的标识符,由[supervisord]块中的identifier选项设置。

serial:event的序列号

pool:listener的pool的名字。

poolserial:event在pool中的的序列号

eventname:event类型名称

len:header后面的body长度。

一个body例子:

processname:foo groupname:bar pid:123
This is the data that was sent between the tags

processname:事件所属的子进程名字

groupname:子进程所属组名

pid:子进程pid

一个简单的listener脚本,listener.py:

import sys

def write_stdout(s):
    # only eventlistener protocol messages may be sent to stdout
    sys.stdout.write(s)
    sys.stdout.flush()

def write_stderr(s):
    sys.stderr.write(s)
    sys.stderr.flush()

def main():
    while True:
        # 进入READY状态
    ┆   write_stdout('READYn')
    ┆   # 读取事件通知的header
    ┆   line = sys.stdin.readline()
    ┆   write_stderr(line)
        # 获取body长度,读取body
    ┆   headers=dict([x.split(':') for x in line.split() ])
    ┆   data = sys.stdin.read(int(headers['len']))
    ┆   write_stderr(data+'n')
        # 发送OK进入ACKNOWLEDGED状态
    ┆   write_stdout('RESULT 2nOK')
if __name__ == '__main__':
    main()

在conf.d目录中建立一个listener配置文件mylistener.conf:

[eventlistener:mylistener]
command=python listener.py
directory=/thedirectoroflistener.py
user=user
events=PROCESS_STATE,TICK_5
stdout_logfile=/path/to/mylistener_stdout.log
stderr_logfile=/path/to/mylistener_stderr.log

启动:

ubuntu:$ sudo supervisorctl start all
mylistener: started
celerybeat: started
ubuntu:$ sudo supervisorctl status
celerybeat                       RUNNING   pid 87729, uptime 0:00:20
mylistener                       RUNNING   pid 87728, uptime 0:00:20

监控就开始了,可以到日志中查看事件通知的内容:

ver:3.0 server:supervisor serial:15361 pool:mylistener poolserial:15361 eventname:PROCESS_STATE_RUNNING len:73
processname:mylistener groupname:mylistener from_state:STARTING pid:87728
ver:3.0 server:supervisor serial:15362 pool:mylistener poolserial:15362 eventname:TICK_5 len:15
when:1514313560
ver:3.0 server:supervisor serial:15364 pool:mylistener poolserial:15364 eventname:PROCESS_STATE_RUNNING len:73
processname:celerybeat groupname:celerybeat from_state:STARTING pid:87729

可以根据自己的需要设定监控的事件类型,然后根据不同的事件类型和内容做出不同的应变,具体的事件类型可以官网查看。

python的supervisor.childutils模块对header和body的处理进行了包装:

def get_headers(line):
    return dict([ x.split(':') for x in line.split() ])

def eventdata(payload):
    headerinfo, data = payload.split('n', 1)
    headers = get_headers(headerinfo)
    return headers, data

def get_asctime(now=None):
    if now is None: # for testing
        now = time.time() # pragma: no cover
    msecs = (now - long(now)) * 1000
    part1 = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(now))
    asctime = '%s,%03d' % (part1, msecs)
    return asctime

class ProcessCommunicationsProtocol:
    def send(self, msg, fp=sys.stdout):
        fp.write(ProcessCommunicationEvent.BEGIN_TOKEN)
        fp.write(msg)
        fp.write(ProcessCommunicationEvent.END_TOKEN)

    def stdout(self, msg):
        return self.send(msg, sys.stdout)

    def stderr(self, msg):
        return self.send(msg, sys.stderr)

pcomm = ProcessCommunicationsProtocol()

class EventListenerProtocol:
    def wait(self, stdin=sys.stdin, stdout=sys.stdout):
        self.ready(stdout)
        line = stdin.readline()
        headers = get_headers(line)
        payload = stdin.read(int(headers['len']))
        return headers, payload

    def ready(self, stdout=sys.stdout):
        stdout.write(PEventListenerDispatcher.READY_FOR_EVENTS_TOKEN)
        stdout.flush()

    def ok(self, stdout=sys.stdout):
        self.send('OK', stdout)

    def fail(self, stdout=sys.stdout):
        self.send('FAIL', stdout)

    def send(self, data, stdout=sys.stdout):
        resultlen = len(data)
        result = '%s%sn%s' % (PEventListenerDispatcher.RESULT_TOKEN_START,
                               str(resultlen),
                               data)
        stdout.write(result)
        stdout.flush()

listener = EventListenerProtocol()

listener脚本可以方便的写做:

import sys

from supervisor import childutils

def write_stdout(s):
    # only eventlistener protocol messages may be sent to stdout
    sys.stdout.write(s)
    sys.stdout.flush()

def write_stderr(s):
    sys.stderr.write(s)
    sys.stderr.flush()

def main():
    while True:
    ┆   headers, payload = childutils.listener.wait()
    ┆   write_stderr(payload+'n')
    ┆   childutils.listener.ok(sys.stdout)

if __name__ == '__main__':
    main()

进程管理工具Supervisor(一)简介与使用

Supervisor是用Python开发的一套client/server架构的进程管理程序,能做到开机启动,以daemon进程的方式运行程序,并可以监控进程状态等等。

linux进程管理方式有传统的rc.d、新兴的upstart、systemd等,与这些相比,Supervisor有着自己的特点。

便利性

使用rc.d管理进程的时候,一是要写耗时耗力的脚本,二是管理的进程挂掉的话不会自动重启。

而supervisor要启动子进程,只需要将子进程的启动命令写入配置文件即可,配置自动重启子进程也很方便。

精确

supervisor作为父进程监控子进程,得到的子进程状态是很准确的。

授权

一般要管理linux进程的话,特别是在一些“low” tcp端口,比如1024以下端口中运行的程序,都需要root权限。

而以root权限启动supervisord后,一般的用户可以在一个简单的shell或者web界面中获取supervisord控制的子进程运行状态,并通过stop、start、restart的命令控制子进程的运行。

进程组

supervisor可以对子进程以start all或者restart all命令的方式进行统一管理。

中央集中式管理

supervisor可以在同一个地方启动、停止或者监视管理的进程。既可以单独的控制一个进程,也可以一起控制一组进程。

管理的时候,既可以本地管理,又可以远程连接,提供命令行接口与web页面接口。

高效性

supervisor是通过fork/exec的方式启动子进程的,子进程挂掉的话,操作系统会立即通知supervisor。

兼容性

除了windows,其他操作系统平台都可运行supervisor。supervisor使用python编写,不需要c编译器。

组件

supervisord

supervisor的服务器端,负责启动子程序,响应客户端发来的命令,重启子程序,记录子程序stdout和stderr的日志,处理Event。

配置文件一般是名为/etc/supervisord.conf的ini文件。

supervisorctl

supervisor的命令行客户端,提供一个类shell接口,用户可以使用supervisorctl连接不同的supervisord进程,查看子进程状态,start、stop子进程,获取控制的子进程列表。

客户端可以使用unix socket或者tcp socket与服务端进行通讯,通过配置文件中的[supervisorctl]段进行配置。

Web Server

使用tcp socket启动supervisord的时候,提供的一个访问supervisor的web接口。

url地址通过配置文件中的[inet_http_server]进行设置。

XML-RPC Interface

一个XML-RPC接口

下载

可以使用setuptools或者pip下载:

easy_install supervisor
or
pip install supervisor

ubuntu中使用apt下载:

apt-get install supervisor

配置文件

运行echo_supervisord_conf命令可以在shell中显示一份supervisor配置文件样本。配置一般是放在文件/etc/supervisord.conf的,所以可以将配置输入到配置文件中:

echo_supervisord_conf > /etc/supervisord.conf

上述命令需要root权限,如果没有权限或者想另置配置文件地址,可以在当前目录生成:

echo_supervisord_conf > supervisord.conf

然后启动时使用-c选项指定配置文件即可:

supervisord -c supervisord.conf

启动

首先配置要启动的子进程,可以在supervisord.conf中配置,也可以单独配置,supervisord.conf中的include配置:

[include]
files = /etc/supervisor/conf.d/*.conf

所以可以在/etc/supervisor/conf.d/目录下,以.conf作为扩展名为子进程建立单独的配置文件,比如建立一个foo.conf文件:

[program:foo]
command=/bin/cat

上述是最简单的配置了,foo为子进程名称,command是子进程运行命令。

重启supervisor使配置生效:

service supervisor restart

或者直接执行启动supervisord:

$BINDIR/supervisord

启动foo子进程:

supervisorctl start foo

一个实际例子,启动celery beat服务,建立celerybeat.conf文件:

; ================================
;  celery beat supervisor example
; ================================

[program:celerybeat]
; Set full path to celery program if using virtualenv
command=celery beat -A myapp --schedule /var/lib/celery/beat.db --loglevel=INFO

; remove the -A myapp argument if you aren't using an app instance

directory=/path/to/project
user=nobody
numprocs=1
stdout_logfile=/var/log/celery/beat.log
stderr_logfile=/var/log/celery/beat.log
autostart=true
autorestart=true
startsecs=10

; Causes supervisor to send the termination signal (SIGTERM) to the whole process group.
stopasgroup=true

; if rabbitmq is supervised, set its priority higher
; so it starts first
priority=999

启动:

$ supervisorctl start celerybeat
celerybeat: started

celery beat服务开始运行了:

[2017-12-22 07:13:47,904: WARNING/ForkPoolWorker-1] 32
[2017-12-22 07:13:47,908: INFO/ForkPoolWorker-1] Task tests.tasks.add[77706e41-83c1-4545-922b-b77a57205ef5] succeeded in 0.00359446300718s: None
[2017-12-22 07:13:57,903: INFO/MainProcess] Received task: tests.tasks.add[3f9c7c88-61aa-44cc-a517-14f32305ad64] 
[2017-12-22 07:13:57,907: WARNING/ForkPoolWorker-1] 32
[2017-12-22 07:13:57,910: INFO/ForkPoolWorker-1] Task tests.tasks.add[3f9c7c88-61aa-44cc-a517-14f32305ad64] succeeded in 0.00268728499941s: None
[2017-12-22 07:14:07,903: INFO/MainProcess] Received task: tests.tasks.add[90e4277b-7041-4c3a-905c-1a5e16272d31] 
[2017-12-22 07:14:07,907: WARNING/ForkPoolWorker-1] 32
[2017-12-22 07:14:07,910: INFO/ForkPoolWorker-1] Task tests.tasks.add[90e4277b-7041-4c3a-905c-1a5e16272d31] succeeded in 0.0029478570068s: None

配置不正确的话,会报supervisor ERROR (spawn error)错误,可以使用tail命令看下错误日志:

supervisorctl tail celerybeat stdout

查看管理的子进程运行状态:

$ supervisorctl status
celerybeat                       RUNNING   pid 66802, uptime 0:12:59

关闭子进程:

supervisorctl stop celerybeat

不加参数的直接运行supervisorctl命令,会进入supervisor客户端的交互终端进行操作。

要通过web页面访问supervisor,可以添加以下配置:

[inet_http_server]
port = 127.0.0.1:9001
username = username
password = yourpwd

浏览器访问127.0.0.1:9001,输入配置的用户名、密码,进入页面:

未分类

Docker资源管理

每个Docker容器在运行时都需要CPU, 内存以及IO资源,而运行Docker容器的主机的资源是有限的,因此如何有效的管理和使用资源就非常的重要了,因此Docker也提供了一下机制可以限制容器使用的资源。

CPU

在使用 docker run 启动容器时,提供了参数-c int或者–cpu-shares int可以帮助我们限制容器对CPU的使用。参数值是一个整数类型,用于设置当前Docker容器使用cpu的权重值(默认为1024)。这是一个相对值,每个容器能够使用的cpu, 取决于它的cpu shares 占所有容器cpu share的比例: 例如:

docker run --name "container1" -c 1024 image
docker run --name "container2" -c 512 image

在上述情况下,如果二者都需要内存,那么 container1 分配到两倍于 container2 的CPU资源。

另外需要注意,这种按权重分配 CPU 只会发生在 CPU 资源紧张的情况下。当CPU还有足够的空闲资源时,是没有此限制的。

内存

我们知道内存包括 实际的物理内存 和 交换内存(swap), 因此 Docker也分别提供了参数来控制内存的使用。

物理内存

在使用 docker run 启动容器时,提供参数-m bytes或者–memory bytes可以帮助我们限制容器对内存的使用。

这里的 bytes 指的是以字节为单位的最大使用限额,指的是明确的限额,而不是一个相对值。

例如:

docker run -m 200M image

此时该容器在运行时可以使用的最大的内存值为 200M, 此时 swap 最大值是物理内存的两倍。

交换内存

在使用 docker run 启动容器时,提供参数–memory-swap bytes可以帮助我们限制容器对swap的使用。

与 -m 参数的使用方式相同,并且二者可以同时使用:

docker run -m 200M --memory-swap=300M image

创建的新镜像可以使用最多 200M 物理内存和 300M swap。

IO

IO资源同样也是操作系统的一种非常重要的资源,通常包含了对硬盘的读写,网络数据的交换等等,这里指关注对 Docker host的硬盘的读写(即block IO)。

控制容器读写硬盘的方法有以下几种:

  1. 设置容器读写硬盘资源的权重
  2. 限制bps和iops

设置权重

在使用 docker run 启动容器时,提供参数–blkio-weight int可以帮助我们控制容器读写磁盘(block IO)的优先级。

这个参数类似于使用 -c 对 cpu资源 的控制方式, 设置的是相对值,而不是绝对值。其含义和分配方式均与 -c 类似,这里就不细说了。

限制 bps 和 iops

  • bps: byte per second,每秒读写的字节数(即读写速率)
  • iops: io per second,每秒 IO 的次数

在使用 docker run 启动容器时, 提供了以下参数来加以控制:

  1. –device-read-bps,限制读某个设备时的 bps。
  2. –device-write-bps,限制写某个设备时的 bps。
  3. –device-read-iops,限制读某个设备时的 iops。
  4. –device-write-iops,限制写某个设备时的 iops。

这里的设置指的就是硬盘,磁盘等等,通过这些参数,可以严格的限制当前容器对某个存储设备的读写速度。

  • –device-read-bps和–device-write-bps参数值的格式为: :[]。device-path 指的就是设备的路径名,number是一个正整数,unit是单位,可以是kb, mb 或者gb

  • –device-read-iops和–device-write-iops参数值的格式为: :。注意这里是没有单位的,因为它表示的就是次数。

例如:

docker run -it --device-write-bps /dev/sda:30MB --device-write-iops /dev/sdb:10000 image

上述命令,限制了新容器对磁盘设备/dev/sda的每秒钟后读写的字节数(bps)为30MB, 以及对 /dev/sdb设备的读写最大次数为10000次.

docker搭建统一开发环境

背景说明:由于公司项目众多,nginx重写复杂,各种缓存等原因导致开发环境搭建很麻烦,开发效率低下。
无需本地安装docker环境。

软件清单

  • docker 服务器
  • rancher 容器管理平台
  • syncthing 文件同步

1. 准备docker环境

用一台配置还不错的服务器,安装好docker环境。

docker安装说明:https://docs.docker.com/engine/installation/

2. 安装rancher

使用rancher主要为了方便管理容器,可视化的界面可以让不会docker的人也能使用。

rancher安装说明:http://rancher.com/docs/rancher/v1.6/zh/

sudo docker run -d --restart=unless-stopped -p 8080:8080 rancher/server:stable

安装好rancher后 访问 http://:8080

3. 添加负载均衡服务

在应用添加负载均衡服务,主要为外部域名访问到不同容器。

4. 添加syncthing

刚开始本来用Samba, 但担心服务器挂掉后代码无法查看,所以才选用syncthing,就算磁盘坏掉都不用担心代码丢失。
在rancher页面上添加或直接run容器,注意需要将数据目录挂载出来

docker run --network=host 
    -v /wherever/st-cfg:/var/syncthing/config 
    -v /wherever/st-sync:/var/syncthing 
    syncthing/syncthing:latest

启动后,访问 http://:8384
使用说明:https://docs.syncthing.net/index.html

5. 本地安装syncthing

syncthing跨所有平台,下载自己系统相应的版本 https://syncthing.net/

下载实时同步插件 Syncthing-inotify https://github.com/syncthing/syncthing-inotify

启动syncthing客户端,web访问 http://127.0.0.1:8384

启动Syncthing-inotify,这样修改文件会实时同步。

添加远程设备,设置远程设备ID(操作->显示ID),服务器端和本地都需要相互添加。

6. 设置需要同步的代码

本地syncthing,添加文件夹,设名称和路径,并共享给服务端,保存。

注意:在同步文件路径根目录添加 .stignore 文件,忽略掉不需要同步的文件或文件夹。例如:

.svn
.git
.idea
down/
phperrorlog/
*.zip
testUnit/
temp/
FonDoc/
tests/
test/

客户端保存后,服务端syncthing,会自动提示有客户端共享文件,修改好服务端保存路径。

7. rancher添加web容器

启动web容器,注意代码挂载路径。也可将php和nginx配置文件挂载出来,便于实时修改。

version: '2'
services:
    dongxu-php56:
        image: dzer/php-meilele-dev:v1
        environment:
          DOCUMENT_ROOT: /app
        stdin_open: true
        volumes:
        - /data/syncthing/dongxu/meilele:/app:rw
        - /data/syncthing/meilele_nginx_conf:/usr/local/nginx/conf:rw
        - /data/syncthing/meilele_php_conf:/usr/local/php/etc:rw
        tty: true
        extra_hosts:
        - common.meilele.com:127.0.0.1
        - memcache.meilele.com:192.168.0.250
        - clubmemcache.meilele.com:192.168.0.250
        - codememc.meilele.com:192.168.0.250
        - zxmemcache.meilele.com:192.168.0.250
        - adminmemcache.meilele.com:192.168.0.250
        - wapmemc.meilele.com:192.168.0.250
        - rule.meilele.com:192.168.0.22
        - ip.meilele.com:192.169.0.250
        - ipadmemcache.meilele.com:192.168.0.250
        - imgmemcache.meilele.com:192.168.0.250
        - dcmemcache.meilele.com:192.168.0.250
        - factory.meilele.com:192.168.0.250
        - zxback.meilele.com:127.0.0.1
        - slave.meilele.com:192.168.0.23

8. rancher lb-service添加域名

在rancher lb中添加需要访问的域名,并指向相应的容器。

9. 修改本地hosts

将需要访问的域名和服务器ip 添加到 本地hosts

CentOS 7上安装Docker

脚本安装

1、使用root权限登陆系统。

2、更新系统包到最新。

yum -y update

3、执行Docker安装脚本

curl -sSL https://get.docker.com/ | sh
yum install -y docker-selinux

4、启动 Docker

systemctl start docker.service

5、验证 docker 已经正常安装

docker run hello-world

yum 安装

1、添加 yum 仓库

cat >/etc/yum.repos.d/docker.repo <<-EOF
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/7
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF

2、安装 Docker 包

yum install -y docker-engine

开机自启动

systemctl enable docker.service