运维 运维环境简单化 - 容器封装

pinewong · 发布于 2016年11月19日 · 最后由 pinewong 回复于 2016年12月05日 · 2222 次阅读
24405
本帖已被设为精华帖!

Overview

一个产品出现, 从线下开发到线上 internal 服务器最后到 release 服务器, 需要维护许多处的环境. 现在, 我们需要将环境同样抽象出来, 做到本地开发环境变了, 所有线上服务器部署前可以一并自动得到同步.

Summary

  • 构建镜像(在项目中添加Dockerfile)
  • 增加部署项目, 例如: app-deploy
  • 新建 Docker Hub 账户, 并绑定你的项目地址(GitHub, Bitbucket)

Quickly realize

1. 构建基础镜像

Docker Docs 里学习镜像相关操作, 最后将现有的环境封装到镜像里, 作为你应用的基础镜像, 并最后上传 Docker Hub.

基础镜像的构建可以参照官方 Library. 例如: Rails 基础镜像

假设我们最后得到基础镜像名为: pinewong/app-base:latest.

2. 构建部署镜像(将基础镜像与项目源码组合)

在项目主目录添加 Dockerfile 文件, 内容不需要太多, 对于 Rails 应用, 类似这样:

FROM pinewong/app-base:latest
MAINTAINER PineWong <pinewong@163.com>

# Bundle
WORKDIR /app
ADD ./Gemfile Gemfile
ADD ./Gemfile.lock Gemfile.lock
RUN bundle install

# Assets
ADD ./app
RUN bundle exec rake assets:precompile RAILS_ENV=production

最后在 Docker Hub 上建立该项目的自动构建关联, 并 Trigger 第一个镜像, 得到部署镜像: pinewong/app:latest

Docker Hub与GitHub项目关联

3. 新建 app-deploy 项目

目录大概如下:

|app-deploy/
|__config/               # 配置文件目录
|__|__nginx_site.conf    # nginx site 设置
|__|__...             
|__docker-compose.yml    # 主配置文件
|__app.default.env       # 默认环境变量
|__...

docker-compose.yml:

version: '2'

services:
  app:
    image: pinewong/app:latest
    command: puma -C config/puma.rb
    depends_on:
      - postgres
    volumes:
      - ./config/nginx_site.conf:/etc/nginx/conf.d/default.conf
  nginx:
    image: nginx:1.11.5
    ports:
      - "80:80"
    depends_on:
      - app
  postgres:
    image: postgres:9.6.1
  # ...

该步骤具体配置可以参照社区 homeland-docker 项目.

homeland-docker install

最后将该项目分发到各服务器和开发人员, 在目录下, 直接: docker-compose up 即可运行这个完全封装的应用.

Advance

开发调试

上述步骤简单封装了一个生产环境, 开发环境只需要做一下小修改, 添加一个 docker-compose-dev.yml 文件, 内容大致是去掉 Nginx 这类生产中用的组件, 和替换一些轻量级开发组件: postgres 替换成 sqlite 等, 并在环境变量配置中修改 RAILS_ENV 值, 最后启动应用时添加文件参数 --file docker-compose-dev.yml

另外开发需要实时变化源码, 我们直接使用容器的 Volume 功能, 将本地源码目录映射替换容器源码目录:

docker-compose.yml:

app:
  volumes:
    - ./app-dir:/app    # app-dir代表本地当前目录下的源码目录,app是上述部署容器所添加源码的目录

最后, 当启动容器运行程序后, 你还可以进到容器内部调试:

docker-compose exec app bash

于是, 你就进到了一个应用环境的命令窗口, 这里可以得到虚拟机的体验.

对于项目的基础镜像管理

为了更好管理各项目的基础镜像, 这里可以学习官方操作, 在自己的 GitHub 中建立一个 Library 源, 这里有我一些环境的例子: pinewong/docker-library

这样还有一个好处, 可以让基础镜像也使用自动构建服务.

简化安装和命令输入

第一次部署项目时具体需要的操作有许多, 例如初始化本地环境变量文件, 数据库初始化, 生成秘钥等等, 这些都可以自动化, 你可以写一个 shell 脚本来做这些, 但更优雅的是学习 homeland-docker 项目, 使用 Makefile 清晰实现封装.

另外如果使用了 Makefile 实现一些封装, 你还可以将开发中的命令简短不少, 例如这样设置 Makefile:

status:
  @docker-compose ps
stop:
  @docker-compose stop
restart:
  @docker-compose restart $(name)
start:
  @docker-compose up -d
console:
  @docker-compose run app rails console
update:
  @make pull
  @make reload
pull:
  @docker pull pinewong/app:latest
  @docker pull nginx:1.11.5
  @docker pull postgres:9.6.1
reload:
  @docker-compose up -d --force-recreate
# ...

之后当你想要进入 Rails 控制台, 可以使用命令make console 代替 docker-compose run app rails console; 当发布新容器, 需要升级时可以使用make update代替

docker pull pinewong/app:latest
docker pull nginx:1.11.5
docker pull postgres:9.6.1
# ...
docker-compose up -d --force-recreate

当然, 你也可以自己在 Makefile 中定义需要的命令.

持续部署

上面虽然解决了环境一致的问题, 但部署由于脱离了 Mina 等工具变得麻烦起来, 这时我们可以使用一些 CI 工具来自动完成, 且上述用到的基本都是标准组件, 持续部署将很容易实现.

需要实现持续部署, 首先你要明确整个流程, 如果我们从项目源码提交开始, 大致是这样:

Commit -> Docker Hub 构建部署镜像 -> 各服务器升级程序(pull 新镜像, 重启容器)

如果是基础环境变了, 例如应用从 MySQL 迁移到 Postgres , 环境依赖需要安装 libmysqlclient-dev, 大致流程变成这样:

docker-library更新 -> Docker Hub 重新构建基础镜像 -> Docker Hub 检测到基础镜像改变 -> Docker Hub 重新构建部署镜像 -> 各服务器升级程序(pull 新镜像, 重启容器)

综上, 不管什么触发, 当我们理解了流程, 我们就可以在 CI 工具中新建一个任务. 开心的是, Docker Hub 和 GitHub 已经结合的相当紧密, 一般的触发可以直接在项目设置 webholk 来实现.最后对于 CI 工具, 我使用的是 Jenkins

Extend

共收到 16 条回复
3 lgn21st 将本帖设为了精华贴 11月20日 01:12
17004
matrixbirds · #2 · 2016年11月20日

厉害了word的哥

370
kgen · #3 · 2016年11月20日

简单应用 Docker 可以胜任。复杂应用,配置管理类平台会可靠得多。

15999
embbnux · #4 · 2016年11月20日

楼主太给面子,一下给了两个参考链接。可以继续看下 docker swarm, 现在比较火的 docker 集群管理方案

24405
pinewong · #5 · 2016年11月20日

#2楼 @matrixbirds 谢谢

#3楼 @kgen #4楼 @embbnux 好的好的,一下又有该学习的东东。

另外 embbnux 我可一直偷偷关注着你啊😀

8744
lithium4010 · #6 · 2016年11月21日

#2楼 @matrixbirds wow难得看到你哇

20859
adamshen · #7 · 2016年11月21日

现在用docker做虚拟化方案的公司还不多,不过趋势已经很明显了

2973
small_fish__ · #8 · 2016年11月22日

Docker 的好处是应用打包(Dockfile),实例一多,容器的管理监控变的比较麻烦,随着 Swarm 合并到 Docker ,不知道这方面是否让它如虎添翼, 不过对其他调度框架也蛮感兴趣 http://dockone.io/article/1138

如果觉得 Docker 不好用,可以尝试 rkt + k8s

另外,对于监控方面,强烈推荐 prometheus, 对于容器监控支持不错,提供告警通知

24405
pinewong · #9 · 2016年11月23日

#8楼 @small_fish__ 嗯嗯,好的好的

61
pzgz · #10 · 2016年12月02日

docker在rails项目的应用上,我一直很纠结的是,怎么能用上capistrano,尝试了几种方式,感觉都有点不舒服。

之前尝试的是放弃capistrano,每次打包一个image,再部署,这个缺点是,asset和bundle install弄不好总是要全部重新写层,所以每次部署的时间都会很长。现在用的方式是,用docker开一个容器,然后对后开好ssh,通过 capistrano部署到这个容器中,部署路径用的是一个data volume,这样换容器没有影响。

这样做,至少在开发阶段,部署的速度能和直接用cap一样了,但其实只是绕路了,和docker本身的意图有点背道。

不知道大家在rails项目上使用docker有啥可以借鉴分享的吗

11562
hging · #11 · 2016年12月02日

#10楼 @pzgz 我感觉本身 rails 的 image 里面并不需要关注 assets 的资源,那么每次打包部署启动的时候只需要在本机编译 assets 然后上传到服务器,container 挂载一个 volumn 加载那个 json 文件就好。这样的好处就是可以使用 capistrano 也不违背 docker 的本意。

61
pzgz · #12 · 2016年12月04日

#11楼 @hging 但是这样的话,每次编译就会在开发本机上做,这当然不是说不可以,不过compile这个过程也是非常耗时耗力的,有时候还是希望扔给服务器。 其实除了这个点,还有另外的一些点,比如说,migrate,还有bundle install,前者的问题是,跑的前提是image已经做好了,需要用一个新的容器来跑一下,后者的问题是,和assets类似,做不好,每次都是要重新安装,相对cap本身来说,速度会慢了很多

29339
Trump · #13 · 2016年12月04日

capistrano已经被我干掉了,自己写脚本调用 git status 和 git push,如果git status的文件变动信息包含 assets 字符串,则执rake assets:precompile,对migrate也是同理,这样小改动部署基本上只需要3 ~ 4秒钟时间(重启server),基本满足了像php那样边写程序边刷浏览器的要求。有了docker迁移服务器环境也不用bundle install了,capistrano基本上可以退休

11562
hging · #14 · 2016年12月04日

#12楼 @pzgz 并不需要每次都从新安装啊 你需要一个装好gem什么的基础image 然后在这个基础上进行修改就行了 每次更新的image容量不会很大。用capistrano一样要本机挂着等啊 在服务器上编译跟在本机编译没什么区别,而且一般情况下本机编译要比服务器快得多。

61
pzgz · #15 · 2016年12月05日

@hging 这倒是一个好办法,我前面说的precompile,虽然在开发机上等,但终究服务器运算还是快,不过你刚才说的这个gem和楼上 @Trump 说的有异曲同工之妙,我太拘泥于cap的方式了,开发上的确cap还是上手快,不过我是可以继续尝试一下在docker上看如何借鉴二位说的方法了,多谢

96
qinix · #16 · 2016年12月05日

用 docker-compose 做开发环境,往代码里插入 binding.pry 时不知道楼主是怎么操作的。

docker-compose 本身不支持 attach 到运行着的容器,给他们提了相关 issue 也没人跟进,最后是自己封装了个命令从 docker-compose 获取容器 id,然后用这个 id 去 docker attach,实现可以在代码中插入 binding.pry 调试。

总的来说,docker-compose 带给我的开发体验并不好,各种小问题很多。我觉得对于 Rails 来说把开发和生产统一成一个环境是个伪需求。Rails 开发涉及到的东西都很容易安装在本地做开发,没必要为了统一环境都用 docker 做开发,丧失体验。Rails 本身已经是个很完善的生态了,开发流程工具都很成熟,何必非要套上一个不成熟的 docker-compose。

而利用 docker-compose 进行部署,我认为也是一个很扯的事情。docker-compose 目前的主要目标还是实现在一个宿主机上进行部署,swarm 还不够成熟,实际部署时候还是要用其他方式多机部署,docker-compose 在这个过程中可能只能起到一个指导作用。

24405
pinewong · #17 · 2016年12月05日

#16楼 @qinix

Docker 如何使用 binding.pry

binding.pry 这类需要终端和进程状态变化的调试操作, 要使用有点违背 Docker 单进程的意图, 但也可以实现. 我的具体做法是在 docker-compose.yml 或 Dockerfile 中修改 CMD 入口, 换一个一直运行的0号进程, 然后 exec 进入终端, 此时的程序终端跟一般状态无异(已经不依赖 Rails 进程了), 之后启动 Rails 进程, 就可以修改代码, 进行终端调试了.

开发体验不好, 但是分使用场景

你说的很对, docker 给 Rails 开发带来的体验是不好, 甚至对于刚接触的人, 因为有一个单进程的限制, 会觉得没有虚拟机方便, 但是这是对后端需要一些高级调试的人来说. 现在假如有一个需求, 一个系统有多个部分, 需要前端, 后端以及一些底层运算成员组成, 对于前端和底层运算成员, 他们没必要本地实现一套 Rails 环境, 他们并不关心这个, 这里可以用虚拟机来实现, 但是虚拟机即使是 vagrant 没有像 Dockerfile 这样使环境完全透明的东西, 运维修改起环境没有 docker 方便.

而对于 Rails 后端人员, 可以用 Docker 运行前端的一套 node 项目和底层运算成员的一些 Maven 项目, 而对于自己的 Rails 项目, 如果开发调试不够用了, 也可以直接使用本地环境代替, 这时候的操作只需要暴露 Node 和其他需要组件的端口, 这里实际上可以理解为将其他不关心的系统封装成一个 app 来跑, 而这些 app 由 Docker Compose 来管理. 当然, 后端对于 Rails 环境也是需要的, 当环境依赖需要修改时, 可以很方便本地在 Docker 中测试好, 之后上传新的 Dockerfile, 在下个版本时, 让其他工程师一并得到更新. 虽然这种需求量不是很多, 但用起来就舍不得放了.

生产环境

楼主还是小白, 没有接触到 swarm 多机部署😂 我现在应用到生产环境, 益处主要是统一了环境, 没有了曾经阿里云运行良好, 然后其他云跑不起来的问题. 最后, Docker Compose 是很不成熟.

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册