按照国际惯例,学习一门编程技术,第一步是从HelloWorld开始。此处,学习Docker也不例外。 下面将通过搭建一个一台机器上的简化的Docker集群,了解如何基于Docker构建一个特定的应用,基于Docker集群构建的应用称为Docker APP Stack,即Docker应用栈。
准备搭建一个包含6个节点的Docker应用栈,其中包括一个代理节点、两个Web的应用节点、一个主数据库节点及两个从数据库节点。具体结构如下:
$ sudo docker pull ubuntu
$ sudo docker pull django
$ sudo docker pull haproxy
$ sudo docker pull redis
$ sudo docker images #查看Docker的所有镜像
运行后应该出来4个镜像,分别是Redis, haproxy, django和ubuntu,否则不正确,请倒回去查看问题。以上完成了Docker镜像的获取。
在Docker中通过–link选项来建立容器间的连接,可以避免容器的IP和端口暴露到外网所导致的安全问题,还可以防止容器在重启后IP地址变化导致的访问失效,它的原理类似于DNS服务器的域名和地址映射。
此应用栈的正确启动顺序为:
redis-master–>redis-slave->APP->HAProxy
注意:以下启动一个容器时,成功后会立即进入到容器中,但是仍需返回到宿主机中继续创建。此时需要返回,请务必使用ctrl+p
ctrl+q
组合键退出,保证容器仍在后台运行。后面的操作也建议这样退出容器。
# 启动Redis容器
$ sudo docker run -it --name redis-master redis /bin/bash
$ sudo docker run -it --name redis-slave1 --link redis-master:master redis /bin/bash
$ sudo docker run -it --name redis-slave2 --link redis-master redis:master /bin/bash
# 启动Django容器
$ sudo docker run -it --name APP1 --link redis-master:db -v ~/Projects/Django/App1:/usr/src/app django /bin/bash
$ sudo docker run -it --name APP2 --link redis-master:db -v ~/Projects/Django/App2:/usr/src/app django /bin/bash
# 启动HAProxy容器
$ sudo docker run -it --name HAProxy --link APP1:APP1 --link APP2:APP2 -p 6301:6301 -v ~/Projects/HAProxy:/tmp haproxy /bin/bash
设定/bin/bash
命令,是给每个新启动的容器分配一个终端执行,为了方便后续与容器进行交互操作。
此时,查看容器运行情况。
$ sudo docker ps
此时正确的情况应该是有6个容器正在运行中,如果您不小心退出了容器,请在上述命令加-a
参数。
由于为了保证容器的轻量化,容器自身没有编辑器,我们也无需给他单独安装编辑器,此处我们使用Volume进行配置的编写。
sudo docker ps
来查看Redis-master的容器ID,并记住前4位,假设是bc8e;接下来使用$ sudo docker inspect --format "{{ .Volumes }}" bc8e
根据书上这样操作,会报错,报错信息为:Template parsing error: template: :1:3: executing “” at <.Volumes>: map has no entry for key “Volumes”
可以通过以下命令解决:
$ sudo docker inspect redis-master | grep Mounts -A 10
此时,会得到一个Source,该地址为volume在主机中的目录,假设为:/var/lib/docker/volumes/0a3d70900bd0fa46c797f7aa7ff5176b7f6a5a798b6a282cd6c532facfd24925/_data。
接下来进入主机的volume目录,利用启动配置文件模板来创建我们的主数据库的启动配置文件,执行命令如下:
$ cd /var/lib/docker/volumes/0a3d70900bd0fa46c797f7aa7ff5176b7f6a5a798b6a282cd6c532facfd24925/_data
$ vi redis.conf
复制以下内容进入文本:
# redis 主配置文件
# 本翻译基于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 ./
################################################################################
# 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
在主机创建好启动配置文件后,切换到容器中的volume目录,并复制启动配置文件到Redis的执行工作目录,然后启动Redis服务器,具体过程如下:
# cd /data
# cp redis.conf /usr/local/bin
# cd /usr/local/bin
# redis-server redis.conf (这是在启动Redis服务器)
# redis 主配置文件
# 本翻译基于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 的基础配置 #
################################################################################
# 设置当本机为slav服务时,设置master服务的ip地址及端口,在Redis启动时,它会自动从master进行数据同步
# slaveof
slaveof master 6379
# 是否作为守护进程运行,生产环境用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 ./
################################################################################
# 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服务器。
同样的,第2个从数据库也一样方式配置。
# redis
127.0.0.1:6379> set master bc8e
OK
127.0.0.1:6379> get master
"bc8e"
随后,在两个slave数据库中分别打开客户端,查看结果。
# redis-cli
127.0.0.1:6379> get master
"bc8e"
首先进入到Django容器中,安装redis数据库。
# pip install redis
验证是否成功:
# python
>>> import redis
>>> print(redis.__file__)
/usr/local/python3.4/site-packages/redis/__init__.py
没有报错的话,证明安装成功。接下来在容器中创建APP:
# 在容器内
# cd /usr/src/app/
# mkdir dockerweb
# cd dockerweb/
# django-admin.py startproject redisweb
# ls
redisweb
# cd redisweb/
# ls
manage.py redisweb
# python manage.py startapp helloworld
# ls
hello manage.py redisweb
转到主机中,利用VIm编辑配置APP文件。
# 在主机内
$ cd ~/Project/Django/App1
$ ls
dockerweb
$ cd dockerweb/redisweb/helloworld/
$ ls
admin.py __init__.py migrations model.py test.py views.py
# sudo vi views.py
文件内容如下:
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
import redis
def hello(request):
str=redis.__file__
str+="
"
r = redis.Redis(host='db', port=6379, db=0)
info = r.info()
str+=("Set Hi
")
r.set('Hi', 'HelloWorld-APP1')
str+=("Get Hi: %s
" % r.get('Hi'))
str+=("Redis Info:
")
str+=("Key: Info Value")
for key in info:
str+=("%s: %s
" % (key, info[key]))
return HttpResponse(str)
接下来完成setting.py文件的配置:
# cd ../redisweb/
# ls
__init__.py __pycache__ settings.py urls.py wsgi.py
# sudo vi settings.py
在settings.py中的INSTALLED_APPS选项下添加helloworld,执行过程如下:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'helloworld',
]
然后,修改urls.py文件:
sudo vi urls.py
from django.conf.urls import include, url
from django.contrib import admin
from helloworld.views import hello
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^helloworld$', hello),
]
注意,书中导入的包还包括:patterns,这有可能导致应用启动出错!!!
最后,在APP容器中执行:
# python manage.py makemigrations
# python detected
# python manage.py syncdb
注意,在执行第3条命令的时候出错了,原因在于书中版本较低,应重复执行第2条命令便可。
同样的,对APP2也这样配置。
最后启动两个应用,在容器中:
# python manage.py runserver 0.0.0.0:8001
在主机的volumes中配置HAProxy的配置文件。
$ cd ~/Projects/HAProxy
$ sudo vi haproxy.cfg
内容如下:
global
log 127.0.0.1 local0 # 日志输出配置,所有日志都记录在本机
maxconn 4096 # 最大连接数
chroot /usr/local/sbin # 改变当前工作目录
daemon # 以后台形式运行HAProxy
nbproc 4 # 启动4个HAProxy实例
pidfile /usr/local/sbin/haproxy.pid # pid文件位置
defaults
log 127.0.0.1 local3 # 日志文件的输出定向
mode http # {tcp|http|health}设定启动实例的协议类型
option dontlognull # 保证HAProxy不记录上级负载均衡发送过来的用于检测状态没有数据的心跳包
option redispatch # 当serverId对应的服务器挂掉后,强制定向到其他健康的服务器
retries 2 # 重试两次连接失败就认为服务器不可用,主要通过后面的check检查
maxconn 2000 # 最大连接数
balance roundrobin # balance有两个可用选项:roundrobin和source,其中,roundrobin表示轮询,
# 而source表示HAProxy不采用轮询的策略,而是把来自某个IP的请求转发给一个固定的IP的后端
timeout connect 5000ms # 连接超时时间
timeout client 50000ms # 客户端连接超时时间
timeout server 50000ms # 服务器端连接超时时间
listen redis_proxy
bind 0.0.0.0:6301
bind-process 2
stats enable
stats uri /haproxy-stats
server APP1 APP1:8001 check inter 2000 rise 2 fall 5 # 你的均衡节点
server APP2 APP2:8002 check inter 2000 rise 2 fall 5
随后,进入到容器中,将文件移动到正确位置。
# cd /tmp
# cp haproxy.cfg /usr/local/sbin/
# cd /usr/local/sbin/
# ls
haproxy haproxy-systemd-wrapper haproxy.cfg
# haproxy -f haproxy.cfg (利用配置文件来启动HAProxy代理)
至此,完成了整个Docker应用栈的配置和部署。
在本地电脑浏览器中,输入应用栈的访问IP+项目名,如:http://172.17.0.9:6301/helloworld可以查看APP1和APP2的内容。注意端口号是6301。
此时,会报错!!!
DisallowedHost at /helloworld
Invalid HTTP_HOST header: '192.168.5.128:6301'.
You may need to add '192.168.5.128' to ALLOWED_HOSTS.
原因在于未开放应用中的允许访问IP。
解决办法,分别修改APP1,APP2的settings.py文件。
ALLOWED_HOSTS = ['访问的IP']
或直接开放全部IP
ALLOWED_HOSTS = ['*']