之前已经给大家简单介绍了docker的基本使用,现在讲解一个实际应用。2个django web应用调取底层redis数据库信息显示到web网页上。然后上层通过haproxy作为web的负载均衡连接器,底层redis数据库一主2从架构,整体架构如下图:
![]()
客户端通过访问haproxy节点,通过默认的轮循策略对应到后端的2台django应用。同时调取后台redis数据库信息显示到web网页上。Redis为一主两从架构,实现读写分离以及保证数据的安全性。
一、准备镜像
从默认hub.docker.com上拉取需要的镜像
root@test:~# docker pull django
root@test:~# docker pull redis
root@test:~# docker pull haproxy
root@test:~# docker images | grep latest
redis latest 5958914cc558 5 days ago 94.9MB
haproxy latest 829b079b1fa5 2 weeks ago 69.5MB
django latest eb40dcf64078 23 months ago 436MB
二、启动容器
启动redis集群
root@test:~# docker run -it --name redis-master redis /bin/bash
root@test:~# docker run -it --name redis-slave1 –link redis-master:master redis /bin/bash
root@test:~# docker run -it --name redis-slave2 –link redis-master:master redis /bin/bash
启动django容器,即应用,在宿主机上创建目录project/Django/app1 app2作为数据卷挂载给容器使用。主要是为了接下来的修改配置文件。
docker run -it --name app1 –link redis-master:db -v ~/project/django/app1:/usr/src/app django /bin/bash
docker run -it --name app2 –link redis-master:db -v ~/project/django/app2:/usr/src/app django /bin/bash
启动haproxy容器,在宿主机上创建目录project/haproxy
docker run -it --name haproxy --link app1:app1 --link app2:app2 -p 6301:6301 -v ~/project/haproxy:/tmp haproxy /bin/bash
查看容器
root@test:~# docker ps -a |egrep "entry|bash"
b5ce94fc1c1f haproxy "/docker-entrypoint.…" 16 hours ago Up 16 hours 0.0.0.0:6301->6301/tcp haproxy
3eb6cd729eff django "/bin/bash" 4 days ago Up 4 days app2
44f4eb740fe6 django "/bin/bash" 4 days ago Up 4 days app1
799966ed2b09 redis "docker-entrypoint.s…" 4 days ago Up 4 days 6379/tcp redis-slave2
ba87fd991694 redis "docker-entrypoint.s…" 4 days ago Up 4 days 6379/tcp redis-slave1
65445c76145d redis "docker-entrypoint.s…" 4 days ago Up 4 days 6379/tcp redis-master
三、配置redis一主两从
配置redis-master容器
首先redis主master容器启动后,我们需要在容器里面配置redis,然后启动redis服务。由于该容器轻量级特性,里面没有vim等编辑器。但是该镜像默认集成了volume的挂载命令,挂载到/data目录下。这样在主机目录下对配置文件进行修改之后,然后复制到容器相应目录中,在启动服务。
首先查看宿主机哪个目录与容器/data挂载
root@test:~# docker inspect 65445c76145d | grep volumes
"Source": "/var/lib/docker/volumes/1a9bbe44d7f75b0be4cfd6f33403255c79af530cc799484be84c230432605ff6/_data",
从本机拷贝一个redis.conf配置文件到该目录
root@test:/var/lib/docker/volumes/1a9bbe44d7f75b0be4cfd6f33403255c79af530cc799484be84c230432605ff6/_data# cp /root/redis-5.0.2/redis.conf .
编辑该文件,修改如下
#bind 127.0.0.1
protected-mode no
daemonize yes
pidfile /var/run/redis.pid
切换到容器中,启动redis服务
root@test:~# docker attach 65445c76145d
root@65445c76145d:/usr/local/bin# cd /data
root@65445c76145d:/data# ls -l
-rw-r--r-- 1 root root 62169 Nov 30 05:44 redis.conf
root@65445c76145d:/data# cp redis.conf /usr/local/bin
root@65445c76145d:/data# cd /usr/local/bin/
root@65445c76145d:/usr/local/bin# redis-server redis.conf
配置2个从redis容器
同样的步骤,修改配置文件如下:
#bind 127.0.0.1
protected-mode no
daemonize yes
pidfile /var/run/redis.pid
slaveof master 6379
启动服务。 这里的slaveof master 6379 其中的master应该为ip地址,但是之前创建容器的时候通过—link参数指定了redis-master的别名master,所以这里写master会自动识别hosts信息,转换为ip地址。
这样redis一主两从部署好之后,测试一下:
root@65445c76145d:/usr/local/bin# redis-cli
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=172.17.0.3,port=6379,state=online,offset=492800,lag=1
slave1:ip=172.17.0.4,port=6379,state=online,offset=492800,lag=1
master_replid:040e8fbe65d4cbf44e1785c6d02aa7ccaf47dc12
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:492800
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:492800
127.0.0.1:6379>quit
可以看到master slave信息且状态都正常。
四、配置django容器
这里的django容器启动后,需要利用django框架开发一个简单的web程序,将redis数据库信息显示在web网页上。所以该容器需要安装redis模块。
进入到django容器app1中,安装redis
pip install redis
然后终端进入到python, import redis 测试一下。
通过之前创建容器时,指定的参数-v project/Django/app1 挂载到容器/usr/src/app目录。登入到容器中,进行app创建。
在容器中
cd /usr/src/app/
mkdir dockerweb
cd dockerweb/
django-admin.py startproject redisweb
cd redisweb/
python manage.py startapp helloworld
切换到主机上project/Django/app1目录中进行配置文件的修改。
root@test:~# cd project/django/app1/
root@test:~/project/django/app1# ls
dockerweb
root@test:~/project/django/app1# cd dockerweb/redisweb/helloworld/
root@test:~/project/django/app1/dockerweb/redisweb/helloworld# ls
admin.py apps.py __init__.py migrations models.py __pycache__ tests.py views.py
root@test:~/project/django/app1/dockerweb/redisweb/helloworld# vi views.py
修改如下:
from django.shortcuts import render
##新增导入http模块
from django.http import HttpResponse
# Create your views here.
##创建自己的view,这里定义的每个函数返回的内容都会显示到web网页上。
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)
上面的定义变量r的时候,Redis()连接数据库。Host定义的为db名字,就是创建容器的时候通过—link 参数指定的redis-master容器。Redis为kv键值对数据库,每个数据记录即是key————value值一一对应,这里不做详细介绍。
上面修改了app信息,接下来修改工程信息,添加新增的helloworld 应用。
root@test:~/project/django/app1/dockerweb/redisweb/helloworld# cd ../redisweb/
root@test:~/project/django/app1/dockerweb/redisweb/redisweb# ls
__init__.py __pycache__ settings.py urls.py wsgi.py
修改settings.py
ALLOWED_HOSTS = ['*'] ##这里设置运行所有的主机访问该应用
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'helloworld', ###这里新增app名字
]
修改urls.py
from django.conf.urls import url
from django.contrib import admin
from helloworld.views import hello ##从自定义的试图中导入函数模块hello,显示到web端
urlpatterns = [
url(r'^admin/', admin.site.urls),
##这里定义浏览器输入x.x.x.x/helloworld 这样形式开头的访问请求,则返回hello函数内容,即之前定义views.py文件中
url(r'^helloword$',hello),
]
修改上述文件后,进入容器完成数据库等的生成。
root@test:~# docker attach 44f4eb740fe6
root@44f4eb740fe6:~#
root@44f4eb740fe6:~# cd /usr/src/app/dockerweb/redisweb/
root@44f4eb740fe6:/usr/src/app/dockerweb/redisweb# python manage.py migrate
启动web应用
python manage.py runserver 0.0.0.0:8001
可以自己测试下:curl 0.0.0.0:8001/helloworld
同样的方法部署app2容器,唯一需要修改的是在视图viewss.py中修改key Hi对应的值变为HelloWorld-app2 已区别2个django应用。
五、部署haproxy容器
之前创建该容器的时候指定了宿主机目录/project/haproxy挂载容器目录,进入到宿主机目录修改haproxy参数
root@test:~/project/haproxy# vi haproxy.cfg
global
maxconn 4096 #默认最大连接数
log 127.0.0.1 local0 #[err warning info debug]
chroot /usr/local/sbin #chroot运行的路径
daemon #以后台形式运行haproxy
nbproc 4 #进程数量(可以设置多个进程提高性能)
pidfile /usr/local/sbin/haproxy.pid #haproxy的pid存放路径,启动进程的用户必须有权限访问此文件
defaults
log 127.0.0.1 local3
mode http #所处理的类别 (#7层 http;4层tcp )
maxconn 2000 #最大连接数
option dontlognull #不记录健康检查的日志信息
option redispatch #serverId对应的服务器挂掉后,强制定向到其他健康的服务器
retries 2 #3次连接失败就认为服务不可用,也可以通过后面设置
balance roundrobin #默认的负载均衡的方式,轮询方式
#balance source #默认的负载均衡的方式,类似Nginx的ip_hash
#balance leastconn #默认的负载均衡的方式,最小连接
timeout connect 5000ms #连接超时
timeout client 50000ms #客户端超时
timeout server 50000ms #服务器超时
####################监控页面的设置#######################
listen redis_proxy
bind 0.0.0.0:6301 #Frontend和Backend的组合体,监控组的名称,按需自定义名称
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
这里app1 app2分别对应之前创建的django应用容器名,--link后自动识别hosts对应的主机名和ip直接的关系,而不需要自己手动去维护。
进入到容器中,启动服务
root@test:~/project/haproxy# docker attach b5ce94fc1c1f
root@b5ce94fc1c1f:/usr/local/sbin# cd /tmp/
-rw-r--r-- 1 root root 1776 Nov 30 07:09 haproxy.cfg
root@b5ce94fc1c1f:/tmp# cp haproxy.cfg /usr/local/sbin/
root@b5ce94fc1c1f:/tmp# cd /usr/local/sbin/
root@b5ce94fc1c1f:/usr/local/sbin# ha
haproxy hash
root@b5ce94fc1c1f:/usr/local/sbin# haproxy -f haproxy.cfg
六、wed端测试
以上完成了所有的容器部署,现在通过web访问测试。大家都还记得haproxy容器创建的时候-p参数通过指定宿主机6301端口与容器6301端口进行互通,即通过主机端口可以访问到容器内部。
访问web,实际输入helloword(之前配置环境遗留问题)
再次刷新网页
至此,完成部署测试。
dockerfile介绍
Dockfile是一种被Docker程序解释的脚本,Dockerfile由一条一条的指令组成,每条指令对应Linux下面的一条命令。Docker程序将这些Dockerfile指令翻译真正的Linux命令。Dockerfile有自己书写格式和支持的命令,Docker程序解决这些命令间的依赖关系,类似于Makefile。Docker程序将读取Dockerfile,根据指令生成定制的image。相比image这种黑盒子,Dockerfile这种显而易见的脚本更容易被使用者接受,它明确的表明image是怎么产生的。有了Dockerfile,当我们需要定制自己额外的需求时,只需在Dockerfile上添加或者修改指令,重新生成image即可,省去了敲命令的麻烦。
这里通过自己编写dockerfile 部署上述环境中的6个容器基础镜像。这样下次换一个环境之后,只需要pull下新的镜像既可以部署应用,而不需要像之前那样手动修改参数等等复杂操作。
创建各个镜像单独目录,目录架构如下
root@test:/tmp# tree
.
├── django-app1
│ ├── dockerfile
│ ├── settings.py
│ ├── urls.py
│ └── views.py
├── django-app2
│ ├── dockerfile
│ ├── settings.py
│ ├── urls.py
│ └── views.py
├── haproxy
│ ├── dockerfile
│ └── haproxy.cfg
├── redis-master
│ ├── dockerfile
│ └── redis.conf
└── redis-slave1
├── dockerfile
└── redis.conf
5 directories, 14 files
Redis-master目录
root@test:/tmp/redis-master# cat dockerfile
FROM redis:latest
MAINTAINER zhangtao 390970723@qq.com
##复制配置文件redis.conf
COPY ./redis.conf /usr/local/bin
WORKDIR /usr/local/bin
RUN chown root:staff redis.conf
ENTRYPOINT ["redis-server","redis.conf"]
root@test:/tmp/redis-master#
这里的redis.conf就是之前的redis-master配置文件,注释掉daemonize yes这行,否则容器启动后会自动退出。让进程在容器前端运行。
Build新镜像,并启动容器测试。
docker build -t redis-master:dockerfile .
docker run -d --name redis-m-dockerfile redis-master:dockerfile
测试docker exec -it b623590b00d1 redis-cli
Redis-slave目录
root@test:/tmp/redis-slave1# cat dockerfile
FROM redis:latest
MAINTAINER zhangtao 390970723@qq.com
##复制配置文件redis.conf
COPY ./redis.conf /usr/local/bin
WORKDIR /usr/local/bin
RUN chown root:staff redis.conf
ENTRYPOINT ["redis-server","redis.conf"]
root@test:/tmp/redis-slave1#
和master一样的dockerfile,但是配置文件redis.conf为之前redis-slave1的配置文件,注释掉daemonize yes这行。
启动容器slave1 slave2
通过下面创建容器的时候--link 指定别名为master 。
docker build -t redis-slave1:dockerfile .
docker images
docker run -d --name redis-slave1-dockerfile --link redis-m-dockerfile:master redis-slave1:dockerfile
docker run -d --name redis-slave2-dockerfile --link redis-m-dockerfile:master redis-slave1:dockerfile
Django-app1目录
root@test:/tmp/django-app1# cat dockerfile
FROM django:latest
MAINTAINER zhangtao 390970723@qq.com
#在容器内安装redis模块
RUN pip install redis
#创建app相关目录
RUN mkdir -p /usr/src/app && cd /usr/src/app && mkdir dockerweb && cd dockerweb && django-admin.py startproject redisweb\
&& cd redisweb && python manage.py startapp helloworld
#替换应用app的视图-即web显示部分
COPY ./views.py /usr/src/app/dockerweb/redisweb/helloworld/
#替换工程调用文件,-即redisweb增加调用app部分
COPY ./settings.py /usr/src/app/dockerweb/redisweb/redisweb/
COPY ./urls.py /usr/src/app/dockerweb/redisweb/redisweb/
#指定工程目录路劲,并启动django web服务
WORKDIR /usr/src/app/dockerweb/redisweb
RUN python manage.py migrate
CMD ["python","manage.py", "runserver", "0.0.0.0:8001"]
Build镜像
docker build -t django:dockerfile1 .
docker run -d --name django-app1-dockerfile -p 8001:8001 --link redis-m-dockerfile:db django:dockerfile1
上面加入-p 参数是为了 指定本机8001端口与容器8001进行互通,让外面通过主机端口访问到容器内部应用。
web端测试:
http://192.168.0.250:8001/helloworld
Django-app2目录
第二个django
dockerfile中定义端口为8002。另外views.py文件中定义key Hi值为app2。
修改views.py 改参数 修改dockerfile指定端口8002
docker build -t django:dockerfile2 . build镜像
docker run -d --name django-app2-dockerfile -p 8002:8002 --link redis-m-dockerfile:db django:dockerfile2
web端测试:
http://192.168.0.250:8002/helloworld
haproxy目录
root@test:/tmp/haproxy# cat dockerfile
FROM haproxy:latest
MAINTAINER zhangtao 390970723@qq.com
##复制配置文件redis.conf
COPY ./haproxy.cfg /usr/local/sbin
WORKDIR /usr/local/sbin
ENTRYPOINT ["haproxy","-f","haproxy.cfg"]
#CMD ["haproxy","-f","haproxy.cfg"]
其中haproxy.cfg配置文件就是之前的文件,注释掉daemon
# daemon #以后台形式运行haproxy
root@test:/tmp/haproxy# docker build -t haproxy:dockerfile .
root@test:/tmp/haproxy# docker run -d --name haproxy-dockerfile --link django-app1-dockerfile:django-app1-dockerfile
--link django-app2-dockerfile:django-app2-dockerfile -p 6302:6301 haproxy:dockerfile
web测试: http://192.168.0.250:6302/helloworld
http://192.168.0.250:6302/haproxy-stats
上传阿里云仓库
登入阿里云仓库,创建仓库
docker login --username=xxxxxx@qq.com registry.cn-hangzhou.aliyuncs.com
给镜像添加tag
docker tag be20dbb59c6e registry.cn-hangzhou.aliyuncs.com/momo/ceshi:redis-master:dockerfile
docker tag be20dbb59c6e registry.cn-hangzhou.aliyuncs.com/momo/ceshi:redis-master.dockerfile
docker tag e6a73c43c7af registry.cn-hangzhou.aliyuncs.com/momo/ceshi:redis-slave1.dockerfile
docker tag 75f05c5be0e8 registry.cn-hangzhou.aliyuncs.com/momo/ceshi:django.dockerfile1
docker tag 7ab005bac9a3 registry.cn-hangzhou.aliyuncs.com/momo/ceshi:django.dockerfile2
docker tag cc35421b35e6 registry.cn-hangzhou.aliyuncs.com/momo/ceshi:haproxy.dockerfile
push到仓库
docker push registry.cn-hangzhou.aliyuncs.com/momo/ceshi:redis-master.dockerfile
docker push registry.cn-hangzhou.aliyuncs.com/momo/ceshi:haproxy.dockerfile
docker push registry.cn-hangzhou.aliyuncs.com/momo/ceshi:django.dockerfile2
docker push registry.cn-hangzhou.aliyuncs.com/momo/ceshi:django.dockerfile1
docker push registry.cn-hangzhou.aliyuncs.com/momo/ceshi:redis-slave1.dockerfile
这样,在其他主机上,直接pull下来后即可以使用。
其他主机测试
[root@node33 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
[root@node33 ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@node33 ~]#
该主机上镜像容器均没有。现在pull镜像
[root@node33 ~]# docker login --username=xxxxxx@qq.com registry.cn-hangzhou.aliyuncs.com
Password:
Login Succeeded
[root@node33 ~]#
[root@node33 ~]#
[root@node33 ~]# docker pull registry.cn-hangzhou.aliyuncs.com/momo/ceshi:redis-master.dockerfile
redis-master.dockerfile: Pulling from momo/ceshi
a5a6f2f73cd8: Pull complete
a6d0f7688756: Pull complete
53e16f6135a5: Pull complete
b761e99e9c9c: Pull complete
13686b3f2e29: Pull complete
667e8fd02be2: Pull complete
6919fef48006: Pull complete
4196dd1ed65a: Pull complete
Digest: sha256:b0a6ccda95d8677e62e64db986ca1d9db8b738d1d0b279b20c867a6e9f8ce534
Status: Downloaded newer image for registry.cn-hangzhou.aliyuncs.com/momo/ceshi:redis-master.dockerfile
[root@node33 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.cn-hangzhou.aliyuncs.com/momo/ceshi redis-master.dockerfile be20dbb59c6e 25 hours ago 95.1MB
[root@node33 ~]# docker run -d --name redis-master registry.cn-hangzhou.aliyuncs.com/momo/ceshi:redis-master.dockerfile
7f504967542acecb7e69c882e6ce5e62fca0e823c2dbfbc79199727987b0f075
[root@node33 ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f504967542a registry.cn-hangzhou.aliyuncs.com/momo/ceshi:redis-master.dockerfile "redis-server redis.…" 4 seconds ago Up 3 seconds 6379/tcp redis-master
[root@node33 ~]# docker exec -it 7f504967542a redis-cli
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:cdc1fc01e41773f54cab8ecb6e20c77f563816ca
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> quit
如上,改镜像直接pull之后,容器应用redis就处于启动状态。其他容器slave1启动后—link参数指定该容器名字即可使用。