利用Docker在Opsworks上实现零停机时间部署方案

想当初还处于风华正茂的年纪时,我总想当个酷炫的小朋友。之所以想找份跟计算机打交道的工作,是因为这样我就能接触到最新的窗口管理器并使用最前沿的软件版本。然而现实总是残酷的,相信大家都经历过那段“一周七天、每天多次重新编译内核”的黑暗时光。

幸运的是我最终坚持了下来(除了有一次遇上120GB Raid0设置丢失,那一回我确实受到了沉重的打击),而且步入成熟期后、我跟每个老头子一样都开始对变化充满恐惧。

出于这个理由,我逡巡了一段时间才真正下决心上手Docker。每个走在时代最前沿的年轻人都在讨论容器技术,但我已经不再身处那种急于彰显自己的岁数,如今的我只是个平均、内敛而且年纪还不算太大的家伙。

单单通过标题,大家可能已经猜到了故事的结局,不过Docker确实让我有了眼前一亮的感觉。它的出现以一种便捷的方式解决了大量常见于开发及部署流程中的难题。

在经过一段时间的学习后,我对自己的应用程序进行重构以保证其顺畅运行在Docker容器当中。而在随后的几周内,我把注意力集中在了Docker的部署方面。我们的应用程序运行在AWS云环境之下,而在众多Amazon服务中、我们选择了Opsworks——理由很简单,易于设置、易于维护、易于扩展。

遗憾的是,Opsworks并不支持Docker,而Amazon自家的容器运行方案ECS目前尚未正式推出生产版本。我需要把自己的复杂应用迁移到Docker当中,而且希望现时现下马上开始进行。要实现这一目标,惟一的办法就是将Docker支持能力引入Opsworks。

在谷歌了一圈之后,我发现一篇教程以及几套GitHub repo,但没有一样能真正满足我的需要。

我的应用程序需要多套容器环境才能实现运作,而我希望找到一种能够实现以下编排需求的方案:

  • 负载均衡器x1(也可以是ELB)
  • 前端Web服务器x1(例如nginx)
  • 用于在不同应用程序服务器之间中转的haproxy x1,旨在实现零停机部署
  • 应用程序容器x2,运行其自己的应用程序服务器
  • 用于缓存的Redis x1(也可以是Elasticache)
  • 作为数据库的PostgreSQL x1(也可以是Amazon RDS)
  • 1个或更多cron任务
  • 1个或更多sidekiq实例用于实现异步处理

除此之外,我希望一切都能以负载水平为基础实现横向规模扩展。这乍看起来似乎是个极为艰巨的任务,但随着我开始编写自己的第一套解决方案,一切开始变得明朗起来。我的灵感源自fig,我喜欢fig那种允许用户在不同容器之间指定彼此关系的设计,我也希望能将这种优势引入到Opsworks当中。

大家可以点击此处查看我的工作成果。届时本文截稿时,其中的README.md文件仍然一片空白,不过我承诺会在未来为大家提供说明文档……就目前而言,请各位先将本文作为仅有的参考资料:)

在对自己的Docker化应用进行部署时,我们首先要做的就是登录AWS、访问Opsworks并点击创建一套堆栈。请如下填写相关表彰:

  • 名称:随便想一个
  • 地区:爱写哪写哪
  • 默认根设备类型:EBS后台
  • 默认SSH密钥:如果大家希望访问自己的实例,请选定一套密钥
  • [其它部分都可保留为默认,当然如果大家很清楚这些项目的意义、也可以根据实际情况填写]
  • 高级
    • 使用自定义Chef cookbook:是
    • 库URL: https://github.com/intinig/opsworks-docker.git
    • 管理Berkshelf:是在拥有了自己的堆栈之后,接下来要做的就是添加一个层。点击“添加一个层”并填写以下表单:

  • 层类型:自定义
  • 名称: Docker
  • 简称: docker

在层创建完成之后,对其配置进行编辑并点击EBS分卷标签。我们需要为每个添加到该堆栈中的实例分配120GB分卷存储空间。大家可能会问为什么。很遗憾,在Amazon Linux/EC2中,Docker只能使用devicemapper实现容器管理,而devicemapper所创建的文件会在正常使用过程中增长到高达100GB。额外的20GB则用于存放我们的镜像。大家可以将存储空间调小一些,甚至干脆不分配EBS分卷,但请注意我们早晚会面临这样的问题。

  • 载入点: /var/lib/docker
  • 总体大小: 120GB
  • 分卷类型: 通用 (SSD)

在此之后,我们对层进行编辑以添加自己的自定义配置方案:

  • 设置

    docker::install, docker::registries, logrotate::default, docker::logrotate

  • 部署

    docker::data_volumes, docker::deploy

我们的这套自定义配置方案能做什么?

  • docker::install非常简便易行,它单纯负责将Docker安装在我们的Opsworks实例当中。
  • docker::registries用于登录私有Docker账户。其应该能够与多种注册类型顺畅协作,不过我个人只在quay.io上进行了测试。
  • logrotate::default与docker::logrotate管理着记录文件的相关设置,旨在避免Docker日志文件无限度膨胀。这项设置假定大家切实向远程系统日志处发送了日志内容,我们使用papertrail实现这一点。

现在让我们添加一款应用程序。在左侧的Opsworks菜单中点击Apps而后选择“add a new one”。

  • 名称: 顶呱呱应用
  • 类型: 其它
  • 数据源类型:这里我选择了RDS,但大家也可以使用Opsworks,或者干脆不使用数据库而直接通过Docker容器或者其它源将数据交付至应用程序。
  • 库类型:其它

现在我们只需要向应用程序当中添加一项环境变量:

  • APP_TYPE: docker

其它一切都将通过(庞大的)Stack JSON完成配置。检查我们的堆栈设置并对内容进行编辑。大家还需要为自己的容器编译一套堆栈json。以下为相关示例代码:

{
  "logrotate": {
    "forwarder": "logspout0"
  },
  "deploy": {
    "amazing application": {
      "data_volumes": [
      {
        "socks": {
          "volumes": ["/var/run", "/var/lib/haproxy/socks"]
        },
        "static": {
          "volumes": ["/var/static"]
        }
      }
      ],
      "containers": [
        {
          "app": {
            "deploy": "auto",
            "image": "quay.io/mikamai/awesome-app",
            "database": true,
            "containers": 2,
            "volumes_from": ["socks", "static"],
            "entrypoint": "/app/bin/entrypoint.sh",
            "command": "bundle exec unicorn -c config/unicorn.rb -l /var/lib/haproxy/socks/%{app_name}.sock",
            "migration": "bundle exec rake db:migrate",
            "startup_time": 60,
            "env": {
              "RANDOM_VAR": "foo"
            },
            "notifications": {
              "rollbar" : {
                "access_token": "",
                "env_var": "RACK_ENV",
                "rev_var": "GIT_REVISION"
              }
            }
          }
        },
        {
          "cron": {
            "deploy": "cron",
            "image": "quay.io/mikamai/awesome-app",
            "database": true,
            "containers": 1,
            "env_from": "app",
            "command": "bundle exec rake cron:run",
            "cron": {"minute": 59}
          }
        },

        {
          "sidekiq": {
            "deploy": "auto",
            "image": "quay.io/mikamai/awesome-app",
            "database": true,
            "containers": 1,
            "env_from": "app",
            "command": "bundle exec sidekiq"
          }
        },
        {
          "haproxy": {
            "deploy": "manual",
            "hostname": "opsworks",
            "image": "quay.io/mikamai/configured-haproxy",
            "volumes_from": ["socks"],
            "env": {
              "REMOTE_SYSLOG": "logs.papertrailapp.com:1234"
            }
          }
        },
        {
          "nginx": {
            "deploy": "manual",
            "image": "quay.io/mikamai/configured-nginx",
            "ports": [
              "80:80",
              "443:443"
            ],
            "volumes_from": [
              "socks",
              "static"
            ]
          }
        },
        {
          "logspout": {
            "deploy": "manual",
            "hostname": "%{opsworks}",
            "image": "progrium/logspout",
            "volumes": [
              "/var/run/docker.sock:/tmp/docker.sock"
            ],
            "command": "syslog://logs.papertrailapp.com:1234"
          }
        }
      ]
    }
  },
  "docker": {
    "registries": {
      "quay.io": {
        "password": "",
        "username": ""
      }
    }
  }
}

哇哦!这些内容可够大家消化一阵子了,对吧?在下一篇文章中,我们将一同走入Stack JSON的世界并了解其中各组成部分的实际意义。

感谢大家前来捧场,让咱们下次再见!

原文链接:http://dev.mikamai.com/post/110066104864/zero-downtime-deployments-with-docker-on-opsworks

你可能感兴趣的:(利用Docker在Opsworks上实现零停机时间部署方案)