一个容器只能运行一个进程,即只提供一种服务,对于用户而言,单一的容器是无法满足需求的。通常用户需要利用多个容器,分别提供不同的服务,并在容器间互相通信,最后形成一个Docker集群。
基于Docker集群构建的应用我们称为Docker APP Stack,即Docker应用栈。
在2019年的今天,为了完整的搭建,确实费了好多力气,因为版本升级和其他一些原因,在搭建过程中会出现很多错误,所以不能再一味的参考书去做,而是多方面查阅资料,然后排错,一步步搞定。当你自己完整的搭建成功,会对Docker以及Docker集群有更深入的认识和理解。
说明一下:所有的搭建是基于KaliLinux,时间2019-11.权限:root。
kali版本:
Linux kali 4.19.0-kali4-amd64 #1 SMP Debian 4.19.28-2kali1 (2019-03-18) x86_64 GNU/Linux
docker版本:
镜像是docker的基础,此次搭建是利用从Dockerhub上已有的Docker资源搭建应用栈。搭建之前,需要对要搭建的集群做以了解:
一个HAProxy代理点,两个Web应用节点,一个主Redis数据库节点,两个从Redis数据库节点,具体结构如下:
其中:HAProxy是负载均衡节点;Redis是非关系型数据库;APP是应用,这里使用的是Python语言,基于Django架构设计一个访问数据库的基础web应用。
1.获取应用栈各节点所需镜像:HAProxy、Redis、Django。
docker pull ubuntu
docker pull django
docker pull haproxy
docker pull redis
下载的过程可能会出现卡顿或者网络连接不通的消息从而断开,重复docker pull命令即可。
然后查看下载的镜像
docker images
2.启动应用栈容器节点
1)先创建主Redis-master
docker run -it --name redis-master redis /bin/bash
-i代表使用交互模式,始终保持输入流开放
-t代表分配伪终端,一般两个参数同时使用 -it。
--name 自定义命名,若无,则随机分配.
然后退出,再创建redis-slave1和redis-slave2
master以‘exit’退出后需要重新启动否则无法关联。关闭窗口的话后台还是运行着的。
2)使用--link进行关联
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
查看redis下的容器
3)启动djiango容器:
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
值得注意的是,连接Redis数据库时,使用了--link参数创建db连接来代替具体的IP地址;
4)最后启动HAProxy
docker run -it --name HAProxy --link APP1:APP1 --link APP2:APP2 -p 6301:6301 -v ~/Projects/HAProxy:/tmp haproxy /bin/bash
为了方便后续与容器进行交互式操作,统一设定启动命令为/bin/bash。在启动每个新的容器时都分配一个终端执行。
启动的容器信息可以查看下
至此,所有搭建应用栈所需容器启动工作已经完成。
下一阶段,应用栈容器节点的配置
搭建好的应用栈容器启动后,需要对它们进行修改和配置,以便实现特定的功能和通信协作。
1.redis-master主数据库容器节点的配置
需要在容器中添加Redis的启动配置文件如果你本机装有redis数据库,在/etc/redis/redis.conf就可以找到此配置文件:
,如果没有配置文件的话你可以直接安装redis然后去目录查看,
或者直接下载redis.conf:https://github.com/yhsong-linux/docker-redis/blob/master/redis.conf
这个配置文件是启动Redis数据库的,不可缺少。
由于容器轻量化的设计,缺乏相应的文本编辑器,这个时候使用volume实现文件的创建。在启动容器时加上-v参数挂载volume,实现在主机和容器之间共享数据,这样就可以直接在主机上创建和编辑数据相关文件,省去了在容器中安装编辑工具的麻烦。
在利用Redis镜像启动容器时,镜像中已经集成了volume的挂载命令,所以需要通过docker inspect来查看所挂载volume的情况。
1)现在就是要找到docker中对应的本地文件的目录,然后将redis.conf复制过去。
docker inspect 4f --format "{{.Volumes}}"
那个4f是容器id的前两位,因为ID号太长,所以docker中可以用id前2位以上的id来使用。
出现了错误,查了一下,解决:
1,使用 “.Config.Volumes” 替换 “.Volumes”
但是没有出现我们想要的目录。
2,直接看容器的所有信息 再grep
docker inspect 4f |grep Mount -A 10
这下好像可以找到,既然是inspect的内容,前期出现问题不妨全部查看下
目录出来了,刚才去/var下了,也发现了这些东西,但是没注意到,没想到竟然擦肩而过。
2)安装一个redis,或者下载redis.conf将配置文件复制到容器的目录下.
可以将注释的内容全部过滤然后复制到相应的目录下:
3)然后编辑配置文件:vim redis.conf
4)主机创建好配置文件后,切换到容器中的volume目录,然后配置:
cd /data/
cp redis.conf /usr/local/bin/
cd /usr/local/bin/
ls
redis-server redis.conf
现在已经将redis.conf复制到相应的docker目录中。
正常的操作是这样的,但是这个过程充满着艰辛和无数的小问题。
这段东西搞了三天吧,因为主从redis不能同步的问题,很是烦人,问题就出在了配置文件:redis.conf中
在测试的时候:
在master的容器中启用redis:redis-server redis.conf 加载配置文件,出现了问题:日志文件没有目录
在这里说一个小细节:从本机复制到容器目录下的配置文件,不是能够正常运行的。
为什么呢?
先看第一个:提示没有日志文件的目录或者文件,在kali里面是有的,但是容器中没有,所以要自行创建文件夹和日志文件,为了保险起见,我还加上了 chmod 777 redis-server.log。防止日志文件写不进去。
5)执行redis-server redis.conf:报错
6)然后再次键入redis-server redis.conf,又出现了问题:缺少文件夹
那就再创建新的文件夹。
值得一提的是,当日志文件没有创建的时候,所有的redis的信息输出都会在终端显示
当创建新的日志文件的时候,所有的日志文件都会输出到log里面:
查看日志:
当从redis试图连接master的时候,日志中也会记录:
下图是slave配置好后连接成功的master中记录的日志文件
这个时候master数据库容器基本完成。
2.从数据库节点配置
最麻烦的还是从数据库容器。也不是麻烦,就是出了问题半天不知道在哪里,各种尝试各种查看。甚至重新run各个容器。或者删除redis 的image,重新下,或者怀疑redis.conf有问题卸载redis,重新安装,拉出redis.conf,对比网上的配置文件。。。等等八万种操作。
和master一样,创建新的日志文件和目录,然后创建系统提醒不存在的目录,两次创建和master一样。
但是要在salve容器中的redis.conf 中要多添加一个: salveof master 6379
就是告诉当前redis数据库,自己是master的从数据库,端口是6379.
写上后保存,然后redis-server redis.conf 启用
但是总是出现这个问题
起初以为是配置文件参数改了,查了好长时间,将网上的配置文件和刚下载的redis.conf 进行对比,发现slaveof是不一样的,在我的机子中没有slaveof这个东西,取而代之的是replicaof,还以为参数错了,但是当我用第二个slave2配置的时候用的参数就是slaveof master 6379.竟然成功连接了,所以不是参数的问题。
看最后一行:bind:不能指定请求地址,说明问题在bind这里。
于是我就将bind ip改成了0.0.0.0
最后连接成功,然后检测:
在住容器中运行:
redis-cli,进入redis,可能会出现连接失败的错误,重新运行redis-server redis.conf即可。
意思就是在住redis中创建master变量master,值为“4fb”。
如果从数据库正常连接的话,也会在redis-slave1和redis-slave2中查看到,否则不然。
从数据库连接成功后查看成功:
slave2的配置和slave1一样。细心配置即可。
至此应用栈的数据可部分就搭建完成,实在不容易。
2.APP容器节点(Django)的配置
(Django:Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,视图V和模版T。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是CMS(内容管理系统)软件。并于2005年7月在BSD许可证下发布。这套框架是以比利时的吉普赛爵士吉他手Django Reinhardt来命名的。)
Django容器启动后,需要利用Django框架,开发一个简单的web程序。
为了访问数据库,需要在容器中安装Python预言的redis支持包:
1)进入Django容器APP1:
pip install redis
python
import redis
print(redis.__file__)
然后测试:
没有报错,说明已经可以用python语言来调用Redis数据库。
下面开始创建web程序,以APP1为例,在容器启动的时候,挂载了
-v ~/Projects/Django/APP2:/usr/src/app的volume
(-v参数就是将容器中的目录对应在本地指定目录下方便进入主机的目录来对新建APP进行编辑。)
2)在容器的/usr/src/app/下,创建APP:
cd /usr/src/app/
ls
mkdir dockerweb
cd dockerweb/
django-admin.py startproject redisweb
ls
cd redisweb/
ls
python manage.py startapp helloworld
ls
这段可能命令看不懂,没关系,先继续往下看
3)在容器内创建APP后,切换到主机的~/Projects/Django/APP1进行相应的编辑来配置APP,过程如下。
#代码如下:
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
#创建自己的view
import redis
def hello(request):
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)
值得注意的是,连接Redis数据库时,使用了--link参数创建db连接来代替具体的IP地址;同理,对于APP2,使用相应的db连接即可。
4)完成view.py的文件修改后,接下来修改Redisweb项目中的配置文件setting.py,添加新的helloworld应用,如下:
5)最后修改redisweb项目中的URL模式文件url.py,它将设置访问应用的URL模式。并为URL模式调用视图函数之间的映射列表:如下,引入helloworld应用的hello视图,并为hello视图添加一个urlpatterns变量
6)在主机修改完这几个文件后,再次进入容器,在/usr/src/app/dockerweb/redisweb下完成项目的生成,如下:
第一次执行出现了语法错误,原来在第七行def hello(request)后没加 ':'.小细节一定注意哈。修改后执行命令:
python manage.py makemigrations
python manage.py migrate
python manage.py syncdb
然后执行syndb创建用户
为什么syndb不能用呢,其实在Django1.9之后,这个命令就不支持了,而且没有提醒。然后查看help文档。
可以看到一个createsuperuser。明显这个就是想要的命令
7)执行:
python manage.py createsuperuser
可以看到,创建成功。
至此,所有APP1容器的配置已经全部完成,另一个APP2容器配置也是同样的过程,只需要将第三步稍作修改即可。完成APP2的配置后,就完成了应用栈的APP部分的全部配置过程。
在启动APP的web服务器时,可以指定服务器的端口和IP地址,为了通过HAProxy容器节点接受外网所有的公共IP地址访问,实现负载均衡,需要指定服务器的IP地址和端口,对于APP1使用8001端口,而APP2使用8002端口,同时,都是用0.0.0.0地址,以APP1为例,启动服务器过程如下:
执行脚本一定要去对应的redisweb目录下。
DjangoAPP的配置就结束了。
3.下面开始HAProxy的配置
这个配置是用来进行负载均衡的,部署一个HAProxy负载均衡的容器节点,所有对应用栈的访问都将通过它来实现负载均衡。
1)首先,利用容器启动是挂载的volume将HAProxy的启动配置文件复制进容器中,执行如下:
cd ~/Projects/HAProxy
vim haproxy.cfg
global
log 127.0.0.1 local0 #日志输出配置,所有日志都记录在本机,通过local0输出
maxconn 4096 #最大连接数
chroot /usr/local/sbin #改变当前工作目录
daemon #以后台形式运行HAProxy
nbproc 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
随后进入容器的/tmp目录下,将启动配置文件复制过来:
cp /tmp/haproxy.cfg /usr/local/sbin/haproxy.cfg
然后再/usr/local/sbin/目录下执行:haproxy -f haproxy.cfg
需要提醒的是,配置文件修改后需要killall proxy,或者重新启动容器也行。
至此,HAProxy的配置全部完成。
测试
整个应用栈的访问时通过HAProxy代理点来进行的,在HAProxy容器节点启动时,通过-p 6301:6301参数,映射了容器访问的端口到主机上,因此可以在其他主机上通过本地主机的IP地址和端口来访问搭建好的应用栈。
在本地机上可以查看IP:
然后访问:http://172.17.0.1:6301/helloworld 来查看APP1或者APP2的内容了
看样子又出现了报错,不过我已经习惯了。。。
它给出了解决方法,先试试吧
在APP1和APP2的setting.py文件里,修改ALLOWED_HOSTS = [ ] 字段:
目录要找对
改完后记得把APP2 下面的配置文件也要一起改
然后再刷新
终于出来了!
具体访问到的APP内容节点会由HAProxy代理进行均衡分配。同时可以访问
172.17.0.1:6301/haproxy-stats查看HAProxy的后台管理页面。
很花样,很nice,很满意,很开心。。。
本地测试通过后,尝试在其主机上通过应用栈入口主机IP地址和暴露的6301端口来访问该应用栈的APP,
用外网其他主机访问:
192.168.142.4是宿主机的IP,
当访问helloworld项目时,出现这个,明显需要添加IP,在APP1和APP2里面的setting.py都加上
然后访问成功。
至此,应用栈的搭建以及测试所有工作结束。用时3天,排错2.5天。。。看着书弄的,书是死的,外界的更新是获得,得思考和发现错误以及解决错误,才能效率高速度快。