docker入门(一):MacOS上搭建docker简单应用栈(附详细说明)

一、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所示。

图2-1. 基于Django架构设计的访问redis数据库的Web应用

各组件的角色如下:

  • 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步完成:

    1. 拉取应用栈各节点的镜像image到本地;
    1. 应用栈容器节点互联和节点启动;
    1. 配置应用栈节点;
    1. 应用栈测试。
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 


参考:https://www.jianshu.com/p/2a873474e976

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342