搭建你的第一个Docker应用栈

搭建你的第一个Docker应用栈

摘要

搭建你的第一个Docker应用栈 , proxy+2app+ master_redis+ 2 slave reids

参考《Docker容器与容器云》第二章

获取需要的镜像

# docker pull ubuntu
# docker pull haproxy
# docker pull redis
# docker pull django
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
redis               latest              1c2ac2024e4b        4 days ago          182.9 MB
django              latest              e8bc70dfddcb        4 days ago          436.1 MB
ubuntu              latest              4ca3a192ff2a        11 days ago         128.2 MB
haproxy             latest              7cc35805808d        12 days ago         138 MB

启动容器

启动redis

# docker run -it --name redis-master redis /bin/bash
# docker run -it --name redis-slave1 --link redis-master:master redis /bin/bash 
# docker run -it --name redis-slave2 --link redis-master:master redis /bin/bash

启动Django

# docker run -it --name APP1 --link redis-master:db -v ~/Projects/Django/APP1:/usr/src/app django /bin/bash
# docker run -it --name APP2 --link redis-master:db -v ~/Projects/Django/APP2:/usr/src/app django /bin/bash

启动haproxy

# docker run -it --name HAProxy --link APP1:APP1 --link APP2:APP2 -p 6301:6301 -v ~/Projects/HAProxy:tmp haproxy /bin/bash

查看启动后的容器信息

#docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
75588f3d4438        haproxy             "/docker-entrypoint.s"   12 hours ago        Up 12 hours         0.0.0.0:6301->6301/tcp   HAProxy
105f54250028        django              "bash"                   12 hours ago        Up 12 hours                                  APP2
ed78fe40c658        django              "bash"                   12 hours ago        Up 12 hours                                  APP1
4d2097f76d30        redis               "docker-entrypoint.sh"   12 hours ago        Up 9 hours          6379/tcp                 redis-slave2
04c4b7b1e720        redis               "docker-entrypoint.sh"   12 hours ago        Up 9 hours          6379/tcp                 redis-slave1
e314cfa79ebf        redis               "docker-entrypoint.sh"   12 hours ago        Up 9 hours          6379/tcp                 redis-master

redis配置模板

# redis 主配置文件
# @author chao
# @e-mail chaopeng@chaopeng.me
# 本翻译基于redis 2.6.10,只作部分翻译,如有部分参数不明确请翻阅英文原文

# 请使用 redis-server 配置文件路径 来启动redis

# 请注意一下配置文件的数据单位,大小写不敏感
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes

################################################################################
#                               redis 的基础配置                                 #
################################################################################

# 是否作为守护进程运行,生产环境用yes
daemonize yes

# 如果作为守护进程运行的话,redis会把pid打印到这个文件
# 主要多实例的时候需要写成不同的文件
pidfile /var/run/redis.pid

# redis监听的端口,注意多实例的情况
port 6379

# 允许访问redis的ip
# 测试环境注释该选项,生产环境把所有允许访问的ip都打一次
# bind xxx.xxx.xxx.xxx

# 关闭无消息的客户端的间隔,0为关闭该功能
timeout 0

# 对客户端发送ACK信息,linux中单位为秒
tcp-keepalive 0

# 数据库的数量,我们的游戏建议为1,然后多开实例
databases 1

################################################################################
#                              redis 的持久化配置                                #
################################################################################

# save 间隔 最小更新操作
# 900秒(15分钟)之后,且至少1次变更
# 300秒(5分钟)之后,且至少10次变更
# 60秒之后,且至少10000次变更
# 如果完全作为缓存开启把save全删了

# save 900 1
# save 300 10
# save 60 2000

# 持久化失败以后,redis是否停止
stop-writes-on-bgsave-error no

# 持久化的时候是否运行对字符串对象进行压缩,算法为LZF
rdbcompression yes

# 文件末尾是否包含一个CRC64的校验和
rdbchecksum yes

# redis存储数据的文件,注意多实例的时候该不同名字或者用不同的工作目录
dbfilename dump.rdb

# redis的工作目录,注意多实例的时候该不同名字或者用不同的工作目录
# 建议用不同的工作目录
dir /temp/


################################################################################
#                                 redis 的限制                                  #
################################################################################

# 设置最多同时连接客户端数量。
# 默认没有限制,这个关系到Redis进程能够打开的文件描述符数量。
# 特殊值"0"表示没有限制。
# 一旦达到这个限制,Redis会关闭所有新连接并发送错误"达到最大用户数上限(max number of 
# clients reached)"
#
# maxclients 128

# 不要用比设置的上限更多的内存。一旦内存使用达到上限,Redis会根据选定的回收策略
#(参见:maxmemmory-policy)删除key。
#
# 如果因为删除策略问题Redis无法删除key,或者策略设置为 "noeviction",Redis会回复需要更多内存
# 的错误信息给命令。
# 例如,SET,LPUSH等等。但是会继续合理响应只读命令,比如:GET。
#
# 在使用Redis作为LRU缓存,或者为实例设置了硬性内存限制的时候(使用 "noeviction" 策略)的时候,
# 这个选项还是满有用的。
#
# 警告:当一堆slave连上达到内存上限的实例的时候,响应slave需要的输出缓存所需内存不计算在使用内存
# 当中。
# 这样当请求一个删除掉的key的时候就不会触发网络问题/重新同步的事件,然后slave就会收到一堆删除指
# 令,直到数据库空了为止。
#
# 简而言之,如果你有slave连上一个master的话,那建议你把master内存限制设小点儿,确保有足够的系统
# 内存用作输出缓存。
# (如果策略设置为"noeviction"的话就不无所谓了)
#
maxmemory 2gb

# 内存策略:如果达到内存限制了,Redis如何删除key。你可以在下面五个策略里面选:
# 
# volatile-lru -> 根据LRU算法生成的过期时间来删除。
# allkeys-lru -> 根据LRU算法删除任何key。
# volatile-random -> 根据过期设置来随机删除key。
# allkeys->random -> 无差别随机删。
# volatile-ttl -> 根据最近过期时间来删除(辅以TTL)
# noeviction -> 谁也不删,直接在写操作时返回错误。
# 
# 注意:对所有策略来说,如果Redis找不到合适的可以删除的key都会在写操作时返回一个错误。
#
#     这里涉及的命令:set setnx setex append
#     incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#     sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#     zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#     getset mset msetnx exec sort
#
# 默认值如下:
#
# maxmemory-policy volatile-lru

maxmemory-policy allkeys-lru

# LRU和最小TTL算法的实现都不是很精确,但是很接近(为了省内存),所以你可以用样例做测试。
# 例如:默认Redis会检查三个key然后取最旧的那个,你可以通过下面的配置项来设置样本的个数。
#
# maxmemory-samples 3

################################################################################
#                               redis 的累加模式                                 #
################################################################################

# 默认情况下,Redis是异步的把数据导出到磁盘上。这种情况下,当Redis挂掉的时候,最新的数据就丢了。
# 如果不希望丢掉任何一条数据的话就该用纯累加模式:一旦开启这个模式,Redis会把每次写入的数据在接收
# 后都写入 appendonly.aof 文件。
# 每次启动时Redis都会把这个文件的数据读入内存里。
#
# 注意,异步导出的数据库文件和纯累加文件可以并存(你得把上面所有"save"设置都注释掉,关掉导出机制)。
# 如果纯累加模式开启了,那么Redis会在启动时载入日志文件而忽略导出的 dump.rdb 文件。
#
# 重要:查看 BGREWRITEAOF 来了解当累加日志文件太大了之后,怎么在后台重新处理这个日志文件。
appendonly no

# 纯累加文件名字(默认:"appendonly.aof")
# appendfilename appendonly.aof

# 纯累加文件的flush频率
# always    ->  每次写入都flush,最安全,资源开销最大
# everysec  ->  每秒 (推荐)
# no        ->  由系统确定

# appendfsync always
appendfsync everysec
# appendfsync no

# 当纯累加文件进行rewrite时,是否需要fsync
# 当且仅当appendfsync = always || everysec 时该参数生效
no-appendfsync-on-rewrite no

# 纯累加文件下次rewrite的比例,与纯累加文件文件的最小size
# 下面的参数意味着纯累加文件会在512mb的时候进行一次rewrite
# 若rewrite后的文件大小为x mb,则下次纯累加文件将会在2x mb时rewrite
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 512mb

################################################################################
#                                redis 的高级配置                                #
################################################################################

# 如果hash中的数量超出hash-max-ziplist-entries,或者value的长度超出
# hash-max-ziplist-value,将改成保存dict,否则以ziphash的方式存储以节省空间。以下同理。
hash-max-ziplist-entries 64
hash-max-ziplist-value 128

list-max-ziplist-entries 64
list-max-ziplist-value 128

set-max-intset-entries 64

zset-max-ziplist-entries 64
zset-max-ziplist-value 128

# 是否resize hash? 如果你设置成no需要在源码做一定的修改以防止有人进行hash攻击
activerehashing yes

################################################################################
#                              redis 的其他配置文件                               #
################################################################################

# 日志配置
# include /etc/redis/log.conf

# 主从配置
# include /etc/redis/slave.conf

# 安全配置
# include /etc/redis/security.conf

# LUA配置
# include /etc/redis/lua.conf

Redis Master主数据库容器节点配置

记得修改两处
daemonize yes
pidfile /var/run/redis.pid
protected-mode no
配置文件放在容器里的 /usr/local/bin/ 下面

Redis Slave从数据库容器节点配置

daemonize yes
pidfile /var/run/redis.pid
slaveof master 6379
protected-mode no
配置文件放在容器里的 /usr/local/bin/ 下面

测试一下redis集群是否配置正确

master里 redis-cli set一下个数据
再在slave里 redis-cli get看看能否得到数据

APP容器节点(Django)的配置

安装python的redis支持包

# pip install redis

测试支持包安装成功,没有报错就是成功

# python
Python 3.4.5 (default, Aug 22 2016, 20:55:07) 
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import redis
>>> print(redis.__file__)
/usr/local/lib/python3.4/site-packages/redis/__init__.py

docker attach APP1 敲下回车 进入容器

# cd /usr/src/app
# mkdir dockerweb
# cd dockerweb
# django-admin.py startproject redisweb
# ls
redisweb 
# cd redisweb
# ls
manage.py  redisweb
# python manager.py startapp helloworld
# ls
helloworld  manage.py  redisweb
#cd helloworld
# vim views.py

views.py 内容如下

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
import redis

def hello(requset):
    str=redis.__file__
    str+="<br>"
    r = redis.Redis(host='db', port=6379, db=0)
    info = r.info()
    str+=("Set Hi <br>")
    r.set('Hi', 'HelloWorld-APP1')
    str+=("Get Hi: %s <br>" % r.get('Hi'))
    str+=("Redis Info: <br>")
    str+=("Key: Info Value")
    for key in info:
        str+=("%s: %s<br>" % (key, info[key]))
    return HttpResponse(str)

cd /usr/local/bin/dockerweb/redisweb/redisweb
修改settings.py

ALLOWED_HOSTS = ['*']
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'helloworld',
]

修改urls.py

from django.conf.urls import * 
from django.contrib import admin
admin.autodiscover()
from helloworld.views import hello

urlpatterns = [ 
   url(r'^admin/', include(admin.site.urls)),
   url(r'^helloworld$', hello),
]

cd /usr/src/app/dockerweb/redisweb
执行如下命令生成项目

# python manage.py makemigrations
No changes detected
# python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK

启动APP1
python manage.py runserver 0.0.0.0:8001
ctrl+p,ctr+q 退出容器APP1,同样的方法操作APP2

配置haproxy容器

docker attach HAProxy
cd /cd /usr/local/sbin
vi haproxy.cfg

global
    log 127.0.0.1   local0
    maxconn 4096
    chroot /usr/local/sbin
    daemon
    nbproc  4
    pidfile /usr/local/sbin/haproxy.pid


defaults
    log     127.0.0.1   local3
    mode    http
    option  dontlognull
    option  redispatch
    retries 2
    maxconn 2000
    balance roundrobin 
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms

listen redis_proxy  
    bind 0.0.0.0:6301
    stats enable
    stats uri /haproxy-stats
    stats auth phil:NRG93012
        server APP1 APP1:8001 check inter 2000 rise 2 fall 5
        server APP2 APP2:8002 check inter 2000 rise 2 fall 5

启动HAProxy
haproxy -f haproxy.cfg
ctrl+p,ctrl+q

访问测试

docker容器所在的宿主机是一个虚拟机,192.168.1.177 ,只有命令行,没有浏览器。
所以要在这个虚拟机所在的实体机192.168.1.174去访问177.

不停的刷新,可以看到一会访问APP1,一会访问APP2.