Docker实战-部署haproxy+django+redis应用

之前已经给大家简单介绍了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参数指定该容器名字即可使用。

发布者

deelaaay

己所不欲,勿施于人。