Docker搭建全栈式应用示例(一)基础篇

用docker搭建全栈式应用

简介

本文将以docker为基础,搭建一套简单的应用案例。本文将假定读者已经有一定的Docker应用基础,如懂得用docker pull下载image,懂得用docker run运行一个container等。该案例的应用架构将采用redis的master-slave模式作为数据存储,以django为框架提供web访问,以haproxy作为负载均衡。其架构图如下.

应用架构图

其中App1和App2属于同一个Django的APP.

准备工作

获取相应的docker images

  • 可以直接从docker官方获取images
$ docker pull django
$ docker pull redis
$ docker pull haproxy
  • 获取成功之后,检查一下images是否真的ok
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
haproxy             latest              46bc5babcc18        3 days ago          139.1 MB
redis               latest              84dbf5edc313        4 days ago          184.8 MB
django              latest              3b0bc67346a2        9 days ago          433.5 MB
  • 检查一下各自的版本(root权限)
    redis版本
# docker run -it redis /bin/bash
root@a3ca293915cb:/data# redis-server -v
Redis server v=3.2.0 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=5382f69a4e75566b

haproxy版本

# docker run -it haproxy /bin/bash
root@b11f07766c49:/# haproxy -v
HA-Proxy version 1.6.5 2016/05/10
Copyright 2000-2016 Willy Tarreau <willy@haproxy.org>

django版本

# docker run -it django /bin/bash
root@1daf8d846018:/# django-admin version
1.9.6

检查完后,可以用docker rm把不用的containers删掉

# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
1daf8d846018        django              "/bin/bash"              2 minutes ago       Exited (0) 6 seconds ago                       sleepy_hypatia
b11f07766c49        haproxy             "/docker-entrypoint.s"   3 minutes ago       Exited (0) 2 minutes ago                       elegant_tesla
a3ca293915cb        redis               "docker-entrypoint.sh"   5 minutes ago       Exited (0) 3 minutes ago                       stupefied_cray
# docker rm `docker ps -a | grep 'Exited' | awk '{print $1}'`
1daf8d846018
b11f07766c49
a3ca293915cb

从上,本文各版本采用如下:

service version
haproxy 1.6.5
django 1.9.6
redis 3.2.0

启动containers

本案例中containers的有关启动配置如下:

container image service ports volumes links
redis-master redis redis-server - master:/data -
redis-slave1 redis redis-server - slave1:/data redis-master:master
redis-slave2 redis redis-server - slave2:/data redis-master:master
app1 django django app - app:/data redis-master:redis
app2 django django app - app:/data redis-master:redis
haproxy haproxy haproxy 6301:6301 haproxy:/data app1:app1 app2:app2

启动redis

  • 启动redis-master
    在本地当前目录下创建子目录master,并将master挂载到container里的/data目录。container名字为redis-master。为了方便调试,这里同时启动交互式模式-it,并同时打开bash

    # mkdir master
    # docker run -it --name redis-master -v `pwd`/master:/data redis /bin/bash
    root@3d187b223f56:/data#
    

    添加一个测试条目,取key为redis_app,该测试条目将被app1跟app2查询:

    root@3d187b223f56:/data# redis-cli
    127.0.0.1:6379> set redis_app "Hello redis app!"
    OK
    127.0.0.1:6379> get redis_app
    "Hello redis app!"
    
  • 启动redis-slave1
    本文案例中将采用两个redis的slave,这里首先启动第一个。container名字为redis-slave1,同时为了方便的连接到redis master,这里直接用--linkredis-mastercontainer连接同时命名为master。其实际效果相当于往/etc/hosts添加一条记录,映射master的域名到redis-master的IP。通过这种方式,就不需要手工的添加master的IP,docker在启动的时候会自动添加。

    # docker run -it --name redis-slave1 --link redis-master:master -v `pwd`/slave1:/data redis /bin/bash
    root@cef73ae816be:/data# cat /etc/hosts
    172.17.0.3      cef73ae816be
    127.0.0.1       localhost
    ::1     localhost ip6-localhost ip6-loopback
    fe00::0 ip6-localnet
    ff00::0 ip6-mcastprefix
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    172.17.0.2      master 3d187b223f56 redis-master
    

    可以看出,--link实际上在/etc/hosts下添加了一条新的映射,分别把redis-master的别名master、container ID以及container name映射到master的IP。

    172.17.0.2      master 3d187b223f56 redis-master
    

    如果没有错误,那么上面在master中设置的条目应该可以查询到:

    root@cef73ae816be:/data# redis-cli
    127.0.0.1:6379> get redis_app
    "Hello redis app!"
    
  • 启动redis-slave2
    与redis-slave1同样的方式启动redis-slave2

    # docker run -it --name redis-slave2 --link redis-master:master -v `pwd`/slave2:/data redis /bin/bash
    root@1f22e9e15e8b:/data# cat /etc/hosts
    172.17.0.4      1f22e9e15e8b
    127.0.0.1       localhost
    ::1     localhost ip6-localhost ip6-loopback
    fe00::0 ip6-localnet
    ff00::0 ip6-mcastprefix
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    172.17.0.2      master 3d187b223f56 redis-master
    

    如果没有错误,那么上面在master中设置的条目应该可以查询到:

    root@cef73ae816be:/data# redis-cli
    127.0.0.1:6379> get redis_app
    "Hello redis app!"
    
  • 启动app1
    app1需要连接到redis服务,因此需要把redis-mastercontainer连起来。同时为其创建app目录,由于app1跟app2共享同一个Django项目,这里就不再单独为他们创建各自的目录。

    # mkdir app
    # docker run -it --name app1 --link redis-master:redis -v `pwd`/app/:/data django /bin/bash
    root@7e6e49321993:/#
    

配置

配置redis

  • 获取redis初始配置

    本文采用的版本为3.2.0, 可通过如下方式获取

    # wget https://raw.githubusercontent.com/antirez/redis/3.2.0/redis.conf
    
  • 配置redis-master

    拷贝redis.confmaster目录.

    # cp redis.conf master/
    

    编辑改配置文件。默认里面有非常多的配置选项,这里只需要关注几个,并改成如下:

    bind 0.0.0.0
    protected-mode yes
    daemonize yes
    logfile /var/log/redis.log
    

    由于已经将./master挂载到container的/data目录,因此本地的改动在container里面也是可见的。

  • 配置redis-slave1

    拷贝redis.confslave1目录.

    # cp redis.conf slave1/
    

    编辑改配置文件。默认里面有非常多的配置选项,这里只需要关注几个,并改成如下:

    bind 0.0.0.0
    protected-mode yes
    daemonize yes
    logfile /var/log/redis.log
    slaveof master 6379
    

    最后一项slaveof将slave1设置为master的slave。同样,由于已经将./slave1挂载到container的/data目录,因此本地的改动在container里面也是可见的。

  • 配置redis-slave2

    配置方式与redis-slave1一致,因此,直接拷贝slave1/redis.confslave2目录.

    # cp slave1/redis.conf slave2/
    

配置Django App

  • 配置app1

    首先需要创建Django项目,这需要登录到app1系统中才能够调用Django命令来创建。

    # docker attach app1
    root@7e6e49321993:/# cd /data
    root@7e6e49321993:/# django-admin startproject redisapp
    root@4b8240acb25a:/data# ls
    redisapp
    root@4b8240acb25a:/data# cd redisapp/
    root@4b8240acb25a:/data/redisapp# ls
    manage.py  redisapp
    

    接下来需要编辑Django代码以支持本案例中一个简单的API:/query/,此API将获取redis中的key为redis_app的值(请注意,该值在上面启动redis的时候已经设置好了),并将结果返回给浏览器。由于container中大量的linux工具都没有,因此需要到主机上去编辑。app1container将app目录挂载进去了,因此在container中的创建的Django项目实际上在app目录下也是可见的。

    • 安卓redis client

      由于django app需要访问redis数据库,因此需要先安装redis client

      root@7e6e49321993:/data/redisapp# pip install redis
      Collecting redis
        Downloading redis-2.10.5-py2.py3-none-any.whl (60kB)
            100% |████████████████████████████████| 61kB 123kB/s
            Installing collected packages: redis
            Successfully installed redis-2.10.5
      
    • 编辑views.py文件

      # vim app/redisapp/redisapp/views.py
      

      初始状态,views.py文件并不存在,添加并编辑,内容如下:

      from django.http import HttpResponse
      import socket
      import redis
      
      
      def query(request):
          r = redis.StrictRedis(host='redis', port=6379, db=0)
          value = r.get('redis_app')
          return HttpResponse('hello world from ' + socket.gethostname() + ', with value: ' + str(value))
      
    • 编辑urls.py文件

      # vim app/redisapp/redisapp/views.py
      

      编辑内容如下,只要是添加views.py中定义的view函数跟url的对应:

      from django.conf.urls import url
      from django.contrib import admin
      
      from redisapp.views import *
      
      urlpatterns = [
          url(r'^admin/', admin.site.urls),
              url(r'^query/', query),
              ]
      
    • 编辑settings.py文件

      # vim app/redisapp/redisapp/settings.py
      

      主要是讲默认生成的redisapp添加到INSTALLED_APPS中,添加完后应该如下:

      INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
          'redisapp'
      ]
      
    • 初始化数据库

      在启动django程序之前,必须初始化数据库。默认情况下,采用的sqlite数据库。

      root@2298dd360bbd:/data/redisapp# python manage.py migrate
      Operations to perform:
        Apply all migrations: admin, contenttypes, sessions, auth
      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 sessions.0001_initial... OK
      

    至此,app1已经全部配置并初始化完毕。

  • 配置app2

    app2与app1共享同一个Django项目,因此不需要为app2做任何特别的配置。

  • 配置haproxy

    haproxy依赖于它的配置文件,在配置文件中必须把app1跟app2都加进去,这样haproxy就可以为他们做load balance了。

    # vim haproxy/haproxy.cfg
    

    编辑内容如下:

    global
      maxconn 50000
      daemon
      pidfile /var/run/haproxy.pid
      log   127.0.0.1       local0
      nbproc 4
      #debug
    
    defaults
      mode http
      balance roundrobin
      maxconn 25000
      option httplog
      option abortonclose
      option httpclose
      option forwardfor
      option redispatch
      option redispatch
    
      retries 3
    
      timeout client  30s
      timeout connect 30s
      timeout server  30s
    
    
    listen redis_proxy
      bind :6301
      mode http
      stats enable
      stats uri /stats
      log   127.0.0.1       local0 debug
      server app1 app1:8001 check inter 2000 rise 2 fall 5
      server app2 app2:8002 check inter 2000 rise 2 fall 5
    

    如果需要调试,可以吧global中的debug打开。特别注意的是关于app1跟app2的配置,必须跟他们启动的端口保持一致。因为在前面已经通过--linkapp以及app2连接进来,因此在写address:port时候可以直接使用app1跟app2。

启动services

启动redis service

  • 启动redis-master service

    • attach redis-master container

      # docker start redis-master
      redis-master
      # docker attach redis-master
      root@3d187b223f56:/data#
      
    • 启动service

      root@3d187b223f56:/data# redis-server redis.conf
      root@3d187b223f56:/data# redis-cli
      127.0.0.1:6379> get redis_app
      "Hello redis app!"
      
  • 启动redis-slave1 service

    • attach redis-slave1 container

      # docker start redis-slave1
      redis-slave1
      root@neil-centos1:hademo# docker attach redis-slave1
      root@cef73ae816be:/data# redis-cli
      
    • 启动service

      root@cef73ae816be:/data# redis-server redis.conf
      root@cef73ae816be:/data# redis-cli
      127.0.0.1:6379> get redis_app
      "Hello redis app!"
      
  • 启动redis-slave2 service

    与启动redis-slave1 service一样,这里不再赘述。

启动app service

  • 启动app1 service

    • attach app1 container

      # docker start app1
      app1
      root@neil-centos1:hademo# docker attach app1
      root@7e6e49321993:/#
      
    • 启动service

      root@7e6e49321993:/data# cd redisapp/
      root@7e6e49321993:/data# python manage.py runserver 0.0.0.0:8001
      Performing system checks...
      
      System check identified no issues (0 silenced).
      
      
      May 16, 2016 - 12:51:39
      Django version 1.9.6, using settings 'redisapp.settings'
      Starting development server at http://0.0.0.0:8001/
      Quit the server with CONTROL-C.
      
      
  • 启动app2 service

    • attach app2 container

       docker start app2
       app2
       # docker attach app2
       root@13d9f084c18b:/#
      
    • 启动service

      root@13d9f084c18b:/data# cd redisapp/
      root@13d9f084c18b:/data/redisapp# python manage.py runserver 0.0.0.0:8002
      Performing system checks...
      
      System check identified no issues (0 silenced).
      May 16, 2016 - 12:56:08
      Django version 1.9.6, using settings 'redisapp.settings'
      Starting development server at http://0.0.0.0:8002/
      Quit the server with CONTROL-C.
      
      

启动haproxy service

  • attach haproxy container

    # docker start haproxy
    haproxy
    # docker attach haproxy
    root@7e0f805cd85c:/#
    
  • 启动service

    root@7e0f805cd85c:/data# haproxy -f haproxy.cfg
    [WARNING] 136/130034 (6) : Proxy 'redis_proxy': in multi-process mode, stats will be limited to process assigned to the current request.
    root@7e0f805cd85c:/data#
    

至此,所有的service已经成功启动。可以通过uri http://<host ip>:6031/query/访问本案例中web service。还可以通过http://<host ip>:6031/stats来查询haproxy的统计信息。

结语

本文中所涉及的配置以及代码可以在这个github repo找到: https://github.com/keysaim/demos/tree/master/docker-redis-django。此外,还可以访问博主的个人博客:https://keysaim.github.io

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

推荐阅读更多精彩内容