一、What & why docker?
1.1 docker是什么?
- docker 的本质是一种容器引擎,即container engine;
- docker可以为application创建容器,该容器具有可移植、轻量级的特点;
- 使用docker,开发者或者运维人员在本地测试通过的容器可以方便地、批量地部署在生产环境中,比如 VMs、云端、OpenStack 集群等。
1.2 为什么使用docker?
docker之所以流行,在于它能够较好地解决目前软件行业的痛点,比如:
- 不同环境之间的迁移成本比较高;
- 生产、测试环境的一致性难以保证;
docker能够较好的避免这些问题。
二、docker应用栈简单实例
2.1 系统环境和docker版本信息
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.13.6
BuildVersion: 17G65
$ docker version
Client: Docker Engine - Community
Version: 18.09.2
API version: 1.39
Go version: go1.10.8
Git commit: 6247962
Built: Sun Feb 10 04:12:39 2019
OS/Arch: darwin/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 18.09.2
API version: 1.39 (minimum version 1.12)
Go version: go1.10.6
Git commit: 6247962
Built: Sun Feb 10 04:13:06 2019
OS/Arch: linux/amd64
Experimental: false
2.2 简单应用栈系统架构
本文将要在docker中搭建一个基于Django架构设计的访问redis数据库的Web应用,该应用由1个网络代理节点、2个Web应用节点、1个redis主数据库节点以及2个redis从数据库节点组成,如图2-1所示。
各组件的角色如下:
- haproxy框架实现网络请求转发代理和负载均衡;
接收来自浏览器的网络请求,根据django应用的负载情况分发给 - django--APP1或者django--APP2;
基于django Web框架的Web应用;
基于Python语言编写; - redis作为django使用的缓存,提高Web应用访问数据的速度和效率;
redis主从架构提高可靠性;
redis-master可以读写;
redis-slave*只读;
为了提升网站的性能,使用基于缓存的key-value数据库redis,redis采用主从架构以提高可靠性;ddjango-APP1和APP2访问redis数据库,来自浏览器的网络请求通过haproxy转发给django-APP1和APP2,然后响应请求。
2.3 搭建docker应用栈
搭建和测试过程大体上分为4步完成:
- 拉取应用栈各节点的镜像image到本地;
- 应用栈容器节点互联和节点启动;
- 配置应用栈节点;
- 应用栈测试。
2.3.1 拉取镜像image
$ docker pull ubuntu
$ docker pull haproxy
$ docker pull redis
$ docker pull django
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
haproxy latest 0b132e159de1 12 days ago 72.2MB
redis latest d3e3588af517 3 weeks ago 95MB
ubuntu latest 7698f282e524 3 weeks ago 69.9MB
django latest eb40dcf64078 2 years ago 436MB
解释:
- docker pull IMAGE_NAME:用来取docker官方repository拉取最新release image,如果要指定image的版本号可以这样:docker pull IMAGE_NAME:VERSION;
- docker images:列出本地的所有image。
2.3.2 节点互联和节点启动
按照顺序依次启动各节点:redis-master -> redis-slave -> django -> haproxy。
# docker rm redis-master 用来移除已有的容器
$ 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
$ 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
$ docker run -it --name HAProxy --link APP1:APP1 --link APP2:APP2 -p 6301:6301 -v ~/Projects/HAProxy:/tmp haproxy /bin/bash
解释 - 以 docker run -it --name APP1 --link redis-master:db -v ~/Projects/Django/APP1:/usr/src/app django /bin/bash 为例:
- docker run:表示启动docker容器。
- 参数-it表示:- 表示参数简写,比如-i即--interactive,-t即--tty,-it表示交互式的,docker容器默认执行完命令后就退出,这里加上-it参数,以免该容器执行完命令后就退出(看上去容器就像是没启动);
- 参数--name表示:--表示参数全称,--name APP1表示容器名称为APP1;
- django:image名称,该image会被放在容器APP1中运行;
- /bin/bash:该容器中执行的命令;
- 参数-v ~/Projects/Django/APP1:/usr/src/app:表示挂载volume,用来容器数据持久化的设置,即容器/usr/src/app目录下的数据会被持久化到宿主机 host的~/Projects/Django/APP1目录;
- 参数--link redis-master:db:容器APP1要link到容器redis-master中,且为redis-master设置别名db;
- docker命令用法可以使用help打印帮助信息, 比如要查看docker run这个命令的用法,使用docker run --help。
注:1个terminal中执行1个docker run命令,因为一旦docker run执行成功便进入到了该容器的工作目录。
列出处于running状态的docker容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2b0601b91e9f haproxy "/docker-entrypoint.…" 6 days ago Up 3 days 0.0.0.0:6301->6301/tcp HAProxy
9ca81c5b0b20 django "/bin/bash" 6 days ago Up 3 days APP2
5eb3aa0300d9 django "/bin/bash" 6 days ago Up 3 days APP1
2649504f9b3c redis "docker-entrypoint.s…" 6 days ago Up 3 days 6379/tcp redis-slave2
3b19ea1de6a0 redis "docker-entrypoint.s…" 6 days ago Up 3 days 6379/tcp redis-slave1
0bcfcd4dfc2b redis "docker-entrypoint.s…" 6 days ago Up 3 days 6379/tcp redis-master
注:列出所有的容器使用命令:docker ps -a。
2.3.3 配置节点
接下来就可以完成容器中如redis和haproxy等服务的配置,按照顺序来依次完成配置:redis-master -> redis-slave -> django -> haproxy。
2.3.3.1 redis-master配置
该部分需要修改redis-master的配置文件redis.conf,并使用该配置来启动redis-master服务。
首先连接到redis-master容器并进入redis-server所在的目录:
$ docker attach redis-master
You cannot attach to a stopped container, start it first
LMA23004071M:~ ycaha$ docker restart redis-master
redis-master
LMA23004071M:~ ycaha$ docker attach redis-master
root@0bcfcd4dfc2b:/data# cd /usr/local/bin
root@0bcfcd4dfc2b:/usr/local/bin#
root@0bcfcd4dfc2b:/usr/local/bin# redis-server redis.conf
注:默认情况下,/usr/local/bin这个目录下不存在redis.conf,因此需要创建一个redis.conf。然而redis组件不包含vim工具,因此需要从该容器对应的host挂载volume目录中创建redis.conf,那么该容器工作目录/data中就会自动同步redis.conf。但是由于MacOS的特殊性,导致host中不存在目录/var/lib/docker/,因此该方法也不行。
最后找到一个方法:可以先在host上编辑redis.conf文件,并使用docker命令直接copy redis.conf到容器工作目录/data中,然后进入该容器/data目录并将该文件copy到/usr/local/bin目录:
redis.conf文件可以从官网下载:https://github.com/antirez/redis/
在host上修改redis.conf,添加如下内容:
daemonize yes
pidfile /var/run/redis.pid
logfile "/usr/local/bin/redis.log"
使用docker命令将host上的redis.conf copy到容器目录中并启动redis服务:
$ docker cp ~/Desktop/redis.conf redis-master:/data
$ docker attach redis-master
root@0bcfcd4dfc2b:/data# cp redis.conf /usr/local/bin
root@0bcfcd4dfc2b:/data# cd /usr/local/bin
root@0bcfcd4dfc2b:/usr/local/bin# redis-server redis.conf
2.3.3.2 redis-slave1 和redis-slave2 配置
redis-master中的redis服务启动后,可以开始配置redis-slave1和redis-slave2:
在host上修改redis.conf,添加如下内容:
daemonize yes
pidfile /var/run/redis.pid
slaveof master 6379
使用docker命令将host上的redis.conf copy到各自容器目录中并启动redis服务:
$ docker cp ~/Desktop/redis.conf redis-slave1:/data
$ docker cp ~/Desktop/redis.conf redis-slave2:/data
$ docker attach redis-slave1
root@3b19ea1de6a0:/data# cp redis.conf /usr/local/bin
root@3b19ea1de6a0:/data# cd /usr/local/bin
root@3b19ea1de6a0:/data#/usr/local/bin# redis-server redis.conf
# 另起一个terminal
$ docker attach redis-slave2
root@2649504f9b3c:/data# cp redis.conf /usr/local/bin
root@2649504f9b3c:/data# cd /usr/local/bin
root@2649504f9b3c:/data#/usr/local/bin# redis-server redis.conf
2.3.3.3 测试redis-master和redis-slave1 & redis-slave2数据是否同步
向redis-master中启动一个redis进程,插入一条record,然后check 该record是否被同步到了redis-slave1和redis-slave2中?
首先在redis-master容器中启动redis进程并开启一个redis-client,然后插入一条record:
root@0bcfcd4dfc2b:/usr/local/bin# redis-server redis.conf
root@0bcfcd4dfc2b:/usr/local/bin# redis-cli
127.0.0.1:6379> set master1 616
OK
然后在redis-slave1和redis-slave2中查看该record是否同步:
$ docker attach redis-slave1
root@3b19ea1de6a0:/data# cd /usr/local/bin
root@3b19ea1de6a0:/usr/local/bin# redis-server redis.conf
root@3b19ea1de6a0:/usr/local/bin# redis-cli
127.0.0.1:6379> get master1
"616"
如果同步成功表明redis数据库节点搭建和配置完成,接下来配置django框架和haproxy网络代理部分。
2.3.3.4 django-APP1和django-APP2配置
django容器启动后,可以开始基于python编写的django框架创建Web application,该application需要访问redis,因此需要在django容器中安装redis模块并check redis模块是否安装成功:
$ docker attach APP1
root@5eb3aa0300d9:/usr/src/app/dockerweb/redisweb# pip install redis
root@5eb3aa0300d9:/usr/src/app/dockerweb/redisweb# python
Python 3.4.5 (default, Dec 14 2016, 18:54:20)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> import redis
>>> print(redis.)
redis.AuthenticationError( redis.__getattribute__(
......
按照如上方法,在django-APP2容器中安装redis模块。
接下来开始在django-APP1中创建application helloworld:
首先进入该容器的工作目录/usr/src/app(在2.3.2中配置的)并创建application:
注:django中一个project中可以包含多个application,django使用django-admin.py来完成project的创建,使用manage.py 来完成application的创建,关于django-admin.py和manage.py 的详细用法可以参考:https://blog.csdn.net/weixin_42134789/article/details/80753001。
# 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
在django-APP1中创建application helloworld后,进入host的挂载volume目录~/Projects/Django/App1:
# cd ~/Projects/Django/App1
# ls
dockerweb
可以看到,在django-APP1容器内创建的application helloworld已经被同步到了host的volume目录下。
在django中,一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应。在这里我们需要修改helloworld应用的视图类,为此直接然后修改helloword应用的视图文件views.py:
# cd dockerweb/redisweb/helloworld
# ls
admin.py __init__.py migrations models.py tests.py views.py
# 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)
注意,连接Redis数据库时,使用–link参数创建db连接来代替具体的IP地址;同理,对于APP2,使用想要的db连接即可。
接下来,修改redisweb项目的配置文件setiing.py,添加新建的helloworld应用:
# cd ../redisweb
# ls
__init__.py __pycache__ settings.py urls.py wsgi.py
# 在setting.py文件中的INSTALLED_APPS选项下添加helloworld:
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'helloworld',
]
最后,修改redisweb项目的URL模板文件urls.py,它将设置访问应用的URL模式,并为URL模式调用的视图函数之间的映射表。在url.py文件中,引入helloworld应用的hello视图,并为hello视图添加一个urlpatterns变量。修改后的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),
]
以上修改完成后,再次进入容器,在目录/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
至此django-APP1的配置完成,django-APP2可以按照上述步骤配置,只是在编辑views.py需要将hello函数中的r.set('Hi', 'HelloWorld-APP1')修改为r.set('Hi', 'HelloWorld-APP2')即可。
2.3.3.5 haproxy配置
在启动APP容器的Web服务器时,可以指定服务器的端口和IP地址,为了通过HAproxy容器节点接受外网所有的公共IP地址访问,实现负载均衡,需要指定服务器的IP地址和端口。对于APP1使用8001端口,而APP2使用8002端口,同时,都使用0.0.0.0地址。以APP1为例,启动服务器的过程如下:
# python manage.py runserver 0.0.0.0:8001
# python manage.py runserver 0.0.0.0:8001
Performing system checks...
System check identified no issues (0 silenced).
September 20, 2016 - 23:16:44
Django version 1.10, using settings 'redisweb.settings'
Starting development server at http://0.0.0.0:8001/
Quit the server with CONTROL-C.
HAproxy容器节点配置
所有对应用栈的访问均通过HAproxy负载均衡代理容器节点实现负载均衡。
首先,将HAProxy的启动配置我呢间复制到容器中,在宿主机的volumes目录~/Projects/HAProxy/下:
# cd ~/Projects/HAProxy/
# vim haproxy.cfg
修改后的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
随后,进入容器的volume目录/tmp下,将Haproxy的启动配置文件复制到HAproxy的工作目录:
# cd /tmp
# cp haproxy.cfg /usr/local/sbin
# cd /usr/local/sbin
# ls
haproxy haproxy-systemd-wrapper haproxy.cfg
然后,利用配置文件启动HAProxy代理:
# haproxy -f haproxy.cfg
三、常见问题和解决方案
Q1. 在2.3.2 节点互联和节点启动中,redis-master、redis-slave1以及redis-slave2均没有指定挂载的volume,如何查看默认的持久化目录?为什么提示找不到该持久化目录?
A1: 使用docker inspect redis-master可以查看redis-master这个容器的详细信息,其中就包括持久化方面的配置:
......
"Mounts": [
{
"Type": "volume",
"Name": "ca6e59825eba5475e7473cb198906f7537cba2ae760ac83da3d10564df27b64b",
"Source": "/var/lib/docker/volumes/ca6e59825eba5475e7473cb198906f7537cba2ae760ac83da3d10564df27b64b/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
......
其中 "Destination": "/data"指明容器工作目录是/data,"Source": "/var/lib/docker/volumes/ca6e59825eba5475e7473cb198906f7537cba2ae760ac83da3d10564df27b64b/_data"指明了该容器对应的宿主机持久化目录,但是查看该目录却发现该目录不存在。
$ cd /var/lib/docker/
-bash: cd: /var/lib/docker/: No such file or directory
这是由于MacOS和Linux的差异导致的,解决方法可以参考:
https://www.jianshu.com/p/f0ad94ba8ba4。
Q2: redis容器目录中找不到redis.conf。
A2: 自己创建一个。
Q3: 在使用docker run命令启动容器时可能会遇到如下问题:
docker: Error response from daemon: Conflict. The container name "/redis-slave1" is already in use by container ......
A3: 移除该容器,并重新启动该容器,比如:
docker rm redis-slave1
docker run -it --name redis-slave1 --link redis-master:master redis /bin/bash