文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

在 dubizzle 我们倾向于采用面向服务的体系架构,这对我们来说非常有意义,但是也意味着在开发的时候必须同时运行很多个服务栈。我们中的一些人会选择 docker-compose,这是一个超级方便用来协调多个容器的方法,同时可以轻松的在开发机中启动和停止服务。本文将指导你搭建这样一个环节,前提是你有一些基础的 Docker 知识。

准备工作

我们假设你在 Linux 上运行过原生的 Docker 环境(这里我们成为“你的 Docker 主机”),而 docker-compose 同样可以在 OSX 下运行良好,包括非常方便的磁盘卷的安装。

第 1 段(可获 1.51 积分)

第一个docker-compose工程

作为第一个示例,我们将要启动一组有依赖关系的容器,写法如下:

version: '2'
services:
  webapp:
    image: <your_web_app_here>:latest
    ports:
      - "80:80"
    depends_on:
      - postgres
      - memcached

  memcached:
    image: memcached:latest

  postgres:
    image: postgres:9.3

在此需要强调一点的是,以上的配置并不提供一个长久的存储方案,只是为了简单的演示,用以上的方法你只能把你的博客文章保存在这些容器里面。推荐下面方法作为存储:创建一个只有数据(data-only)的容器并且命名为存储卷(volumes) (需要docker版本大于1.9.0),尽管我还没有自己尝试过。

第 2 段(可获 1.08 积分)

通过如上的配置,当你执行docker-compose up的时候,包括Memcached和Postgres就都会在你的webapp启动之前平滑的启动了,老版本的docker-compose还需要你定义他们的网络依赖,但是最新版本的docker-compose已经使用了内部DNS来允许容器间的调用,除非你写明不允许(参考docker 网络文档)。PS.我发现如果你在depends_on里面直接声明依赖关系一般也是可以的。

第 3 段(可获 1.15 积分)

如果我们在webapp的容器里面运行像ping postgres这样的命令的时候,是可以得到响应的。Postgres和Memcached的容器也是一样,不过默认配置只暴露了它们相关的端口给docker-compose网络。我们也可以显式的声明宿主机的80端口映射到webapp容器的80端口,之后你就能通过curl your_docker_host:80来获得webapp容器的响应了。

为了确认你是否明白上面发生了什么,我们将慢慢解释这个手册上面的配置信息。

第 4 段(可获 1.13 积分)

我的第二个更有用的 docker-compose 工程

现在我要抛出一些更有用的 docker-compose 技巧了,虽然仍很基础,但算是一个飞跃:

version: '2'
services:
  webapp:
    image: <your_web_app_here>:latest
    ports:
      - "80:80"
    depends_on:
      - postgres
      - memcached
    volumes:
     - /path/to/my/code/on/my/dev/machine:/path/to/the/code/in/the/container
    env_file: env_vars/webapp_env_vars.env

  memcached:
    image: memcached:latest

  postgres:
    image: postgres:9.3

env_vars/webapp_env_vars.env:

DB_HOST=postgres
DB_PORT=5432
DB_USER=some_cool_dev
DB_PASSWORD=OMG_PLS_K33P_4_S3CRET
MEMCACHED_HOST=memcached
MEMCACHED_PORT=11211
SOME_VARIABLE_MY_WEBAPP_READS=a_value_i_use_to_configure_it
第 5 段(可获 0.45 积分)

你应该意识到下面两点:

1)webapp从环境变量(env vars)里提取并声明了一个env_file。这点非常重要,因为我们是通过配置这些环境变量来让一个同样的docker镜像(image)来运行在不同的环境中的,这个灵感来源于12-factor 应用。这些配置文件中的环境变量将在容积运行的时候动态的加载到其中,这也就符合最佳实践的规则,也能避免提交一些涉密的信息进入我们的代码或者版本管理系统。

2)webapp也声明了一个存储卷(volume),在这个例子中,我们也想要得到开发环境,所以我们把我们的代码挂在到了webapp容器中并且用容器中的服务启动,这一步你可以根据自己环境配相关的依赖,所以我只配置了一个样例的路径。但是你只要修改这里就能在容器中得到响应,这点是不是很酷?不,是超级酷。这样我们就不需要每次修改代码都去构建一个新的镜像。

第 6 段(可获 2.21 积分)

精彩时刻!

没错你猜对了,是时候可以启动两个服务了,互相独立又同时有依赖关系的一个工程。有微服务webappone和微服务webapptwo,他们会互相调用,同时也会响应外部web请求。没准你已经猜到了这里面有一个问题,那就是两个80端口将同时映射,不过我们只需要在主机上构建一个负载均衡或者反向代理的服务里处理80端口然后把请求路由给微服务就好了。

version: '2'
services:
  haproxy:
    image: haproxy:1.6.4
    volumes:
      - ./conf/haproxy:/usr/local/etc/haproxy # 假设你的机器上已经有了这个目录了
    ports:
      - "80:80"
    depends_on:
      - webappone
      - webapptwo

  webappone:
    image: <your_web_app_one_here>:latest
    depends_on:
      - postgresone
      - memcachedone
    volumes:
      - /path/to/my/code/on/my/dev/machine/code_for_one:/path/to/the/code/in/the/container
    env_file: env_vars/webapp_one_env_vars.env

  memcachedone:
    image: memcached:latest

  postgresone:
    image: postgres:9.3

  webapptwo:
    image: <your_web_app_two_here>:latest
    depends_on:
      - postgrestwo
      - memcachedtwo
    volumes:
      - /path/to/my/code/on/my/dev/machine/code_for_two:/path/to/the/code/in/the/container
    env_file: env_vars/webapp_two_env_vars.env

  memcachedtwo:
    image: memcached:latest

  postgrestwo:
    image: postgres:9.3

 

第 7 段(可获 0.9 积分)

env_vars/webapp_one_env_vars.env:

DB_HOST=postgresone
DB_PORT=5432
DB_USER=THE_BEST_ALPHA
DB_PASSWORD=OMG_PLS_K33P_4_S3CRET
MEMCACHED_HOST=memcachedone
MEMCACHED_PORT=11211
SOME_VARIABLE_MY_WEBAPP_READS=a_value_i_use_to_configure_it

env_vars/webapp_two_env_vars.env:

DB_HOST=postgrestwo
DB_PORT=5432
DB_USER=THE_BEST_BETA
DB_PASSWORD=OMG_PLS_K33P_4N0TH3R_S3CR3T
MEMCACHED_HOST=memcachedtwo
MEMCACHED_PORT=11211
SOME_VARIABLE_MY_WEBAPP_READS=a_value_i_use_to_configure_it

conf/haproxy/haproxy.cfg:

# 这是一个在 docker 容器中运行的 DEVELOPMENT haproxy 配置,在产品环境使用需要调整

global
        user haproxy
        group haproxy

defaults
        log     global
        mode    http
        option  httplog
        balance roundrobin
        contimeout 5000
        clitimeout 50000
        srvtimeout 50000

frontend http
        bind 0.0.0.0:80
        default_backend host_one # we feel like putting priority on webappone
        acl host_one hdr(host) -i one.something.local
        acl host_two hdr(host) -i two.something.local
        use_backend one if host_one
        use_backend two if host_two

# 提醒, docker-compose 通过内部 DNS 提供主机名支持,我们需要显式的指定端口,并确保没有冲突
backend one
        server webapp_one webappone:80

backend two
        server webapp_two webapptwo:80
第 8 段(可获 0.33 积分)

解释:我们有两个webapp,每个都监听(在自己的容器内)80端口,现在我们用HAProxy监听整个服务的80端口并且根据请求头来路由相关请求到后端服务。为什么是HAProxy呢?其实也不是,你可以用任何反向代理软件像Nginx或者其他的。

所以一个请求的生命周期大概看起来想下面这个样子:

浏览器请求你的docker主机地址(需要你把one.something.local, two.something.local放到你的host文件中(如果是开发环境的话))

然后,假设选择了two.something.local,然后请提供响应

HAProxy:啊,一个人想请求 two.something.local,我将要把这个请求转发给我内网的webapptwo:80,让他看看。

第 9 段(可获 1.49 积分)

webapptwo:

two.something.local的请求到了,这个还真是发给我的!给它服务响应,返回。

再按照路径返回给浏览器,整个请求结束。

福利时间:本地,自签署的SSL

如果你的网站主要在SSL下,并且很难在线下复制一套相同环境。你也可以用SSL启动整套服务环境。

HAProxy从1.5.X版本开始已经支持SSL,文档看这里。你可能还记得我们之前挂在过一个HAProxy的配置文件。现在我们讲重用这个(不是一个最好的配置,但是我们这里仅仅是做个演示)来服务HTTPS的流量。我们需要做下面两件事情:

  1. 启动HAProxy容器并监听443端口(看到这里,你应该已经知道怎么允许HAProxy来监听443端口)
  2. 把自签名的证书放到我们的HAProxy容器里面(根据我们之前的配置,我们已经挂载了一个存储卷,所以我们只需要把我们的.crt证书放到那个目录就可以了)
  3. 告诉我们的haproxy.cfg用自签名的证书来启动,添加: bind 0.0.0.0:443 ssl crt /usr/local/etc/haproxy/self.pem到你的前端http配置块

通过上面的步骤,两个webapp已经可以通过同意的端口在docker下启动,但是需要注意的是你的webapp本身还是接受的是HTTP的请求

第 10 段(可获 3 积分)

附加课程

docker-compose的古怪之处:

如果你想要改变一下信息,需要完全删除你的容器:

  1. 改变环境变量
  2. 改变监听端口
  3. 自己修改的其他内容

不过这些已经在最近的版本的docker-compose变得更易用,但是有时也不是很稳定。

推荐alias

为了避免重复,alias docker-compose 到 dc

数据卷问题

不确定这个是不是还是个问题,但是你可能会发现(尤其是短时间挂在的数据卷)会很快的用满你的硬盘。也许你需要学习如何清理了(译者注:可能是容器)。这可能是最近遇到ide问题,但是其他的功能还算都正常

第 11 段(可获 1.45 积分)

挂载代码,但是修改代码后不要忘了重启服务

当你挂载了代码到容器里面,修改代码之后并不意味着服务就会自动重启,你需要手动重启它。就像你本地的开发服务器一样:

  1. 如果你弄乱了任何容器,恢复的方法就是直接删除然后启动一个新的
  2. 从今以后你将不再需要安装一堆相关依赖(还有可能与现有的服务发生冲突)在你资源局促的开发机上了,你能把所有需要的东西都升到最新并不会遇到任何麻烦[1]。
第 12 段(可获 1.35 积分)

要么全部使用,要么不用

你要么全用要么不用docker-compose,不要尝试在开发工作中只是一部分使用docker-compose。如果一个依赖文件需要修改导致你需要重新构建镜像,修改配置文件,不要犹豫,不要手动重新安装依赖,你将浪费时间。

保持docker和docker-compose是最近版本,docker在过去的几年里面开发的非常迅速,deocker-compose也是如此。他们不断的招人,解决问题,不过这也意味着他们会不停的更新老的接口,不要等到接口不能用的时候在寻求解决,那是最差的时间。这样做以后你的配置降更加清晰更加可维护。

 

第 13 段(可获 1.63 积分)

不要过早的放弃。你早晚会得到学习docker-compose这个投资的回报。如果你正在使用docker-compose,这会让你能专注的思考你的产品而非其他。当一些人已经忘记了nginx配置里SLIGHTLY不同情况的行为不同的时候,你需要面对的陌生的nginx配置和你日常的开发。你自己的环境更加贴近生产环境。就像开发和测试都在生产和缓冲环境一样。享受其带来的方便。

第 14 段(可获 1.36 积分)

你来构建,假想他人来维护

即使你是刚刚开始使用docker-compose,编写配置,也要像真实的项目一样检查你的修改。这样做的意义在于任何感兴趣的人都能拿来使用或者帮助改善他。“啊,我听说你有一个能够一键启动整个环境的系统?”,“当然,去看我的代码仓库就好了”。

自动构建集群的一些注意事项

一些软件(像ElasticSearch)默认已经能够自动扩展集群,如果你有两个服务同事服务后端服务,为了防止他们都自动扩展,有如下几条建议来避免:

  1. 网络隔离
  2. 在某些配置里面禁用集群,依赖于软件
  3. 显式的指定那些即将停止的会依赖其他容器数据的容器在集群中的名字(在ElasticSearch的例子中)
第 15 段(可获 1.9 积分)

docker-compose.yml 模板的一些考虑

我自己有一套从docker-compose.yml生成的模板。这样就使得切换不相关的服务变得更加容易,组合了haproxy 配置的模板之后你会感觉非常良好。这样你就可以关上你的只运行了部分开发环境的开发机了。

可以参考的模板语言像:jinjia2

尾记

[1] - 有时webapp需要一个非常特殊的版本的库的支持,在mac上安装相比linux是非常困难的。这一点是我用docker作为开发来说最大的好处,我只需要拉下容器然后运行就好了。

第 16 段(可获 1.45 积分)

文章评论