阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Docker在IFTTT的开发和实践

224次阅读
没有评论

共计 7117 个字符,预计需要花费 18 分钟才能阅读完成。

IFTTT 是“if this then that”的缩写,事实上是让你的网络行为能够引发连锁反应、让你使用更为方便,其宗旨是“Put the internet to work for you”(让互联网为你服务)。Docker 在 IFTTT 中也在开发实践,以下是 Nicholas Silva 的一些介绍。

Docker 在 IFTTT 的开发和实践

IFTTT 目前正处于从基础架构向容器集装体系结构转移的过程中。我们有一大批微服务和容器将会按照这个架构进行管理。移动我们的生产环境架构之前,我们决定首先从本地开发环境开始实践。这样我们就可以在冒险上生产环境之前发现应用程序的的一些问题。

另外,本地开发环境已经偏离了我们现存的生产环境。我们使用 Chef(一个系统集成框架,为整个架构提供配置管理功能)和 Vagrant(一款用来构建虚拟开发环境的工具)提供管理本地虚拟机。它虽然一直在工作,但是我们知道它工作不了太久。我们需要采取行动,而不是浪费时间把测试环境同步到一个即将弃用的生产环境中。于是我们决定直接完全跃过现存系统,直接开发我们想要的。

下面介绍的是我们如何让工程师在我们的开发环境运行使用 Docker 的过程。

目前 IFTTT 的工程师都使用苹果电脑开发,所以这里的系统都是 Mac OS,由于不考虑跨平台所以不会太复杂。

我们在 Dash 项目中开源了所有收集的代码。这些代码相当难以理解,如果你盲目的运行必定会浪费很多时间。运行之前我们先来看下它都是做什么的。

Part1:启动项目

我们使用 Homebrew 和 Ansible 运行 curlbash 自动化操作整个过程:

bash <(curl -fsSL https://raw.githubusercontent.com/IFTTT/dash/master/bin/bootstrap)

引导脚本安装 Homebrew 和 Ansible 成功后下载 Dash 的代码库,使用 Ansible 安装并配置 VirtualBox、Docker、Docker Machine、Docker Compose 以及开发环境的 DNS,然后编译 Docker Machine VM。

Ansible 通常被用来管理远程服务器,但也可以用来配置本地机器。通过参数目录中的 IP host 参数 127.0.0.1,你可以使用 Ansible palybook 执行一个本地任务。

ansible-playbook /usr/local/dev-env/ansible/mac.yml -i 127.0.0.1, --ask-become-pass

IP 地址之后必须有一个逗号,因为它使参数显示为列表,而不是一个文件的名称。

Ansible playbook 只是一个 YAML 文件,它列举了一些正在运行的任务和状态。我不会深入讲这个文件,但是如果你有兴趣可以看整个文件。

通过 Ansible,我们安装了提供 Homebrew 不包含二进制文件 Caskroom,以及大量的 packages 和配置文件。
包括:

  • VirtualBox
  • Docker
  • Docker Machine
  • Docker Compose
  • DNS .dev resolution to the VM
  • NFS exports
  • Shell Environment

这里比较有意思的是 DNS 解决方案。在 /etc/resolver/dev 创建文件:

nameserver 192.168.99.100

所有的.dev 请求被路由到 Docker Machine 中。运行一个简单的 dnsmasq 容器,路由所有的.dev 请求回到 VM,另一个 nginx 代理容器路由请求到合适的容器内(待续)。不要去修改 /etc/hosts 文件!来自虚拟机和你本机系统的 Host 类似 ifttt.dev 的域名时,请求可以被路由到合适的服务器。

Part2:创建 Docker Machine

在 dev 命令中,我已经把几个复杂的命令通过别名的方式整合为一个简单文件命令。例如创建一个 dev machine 我们使用:

docker-machine create \
--driver virtualbox 
docker-machine scp \
/usr/local/dev-env/docker/bootsync.sh \
dev:/tmp/bootsync.sh
docker-machine ssh dev \
"sudo mv /tmp/bootsync.sh /var/lib/boot2docker/bootsync.sh" docker-machine restart dev

这条命令意思显而易见。使用 NFS 和 dev DNS,我们要拷贝下面这个脚本到 VM 中,然后重启 VM。

这个脚本并不是很复杂:

#!/bin/sh
sudo umount /Users
sudo /usr/local/etc/init.d/nfs-client start
sleep 1
sudo mount.nfs 192.168.99.1:/Users /Users -v -o \
rw,async,noatime,rsize=32768,wsize=32768,proto=udp,udp,nfsvers=3 
grep '\-\-dns' /var/lib/boot2docker/profile || {
echo 'EXTRA_ARGS="$EXTRA_ARGS --dns 192.168.99.100 \
--dns 8.8.8.8 --dns 8.8.4.4"' | sudo tee -a \
/var/lib/boot2docker/profile
}
echo -e "nameserver 8.8.8.8\nnameserver 8.8.4.4" \
| sudo tee /etc/resolv.conf 

首先卸载掉标准的 vboxfs 并开启 NFS 客户端,然后挂载本机创建的共享目录。Docker Machine 的引导程序会在这个文件目录存在的情况下被同步执行。

我也是尝试使用多种方式运行挂载命令,却很少成功。最终成功搞定后,我就成为 NFS 专家了。

首先去 dnsmasq 容器设置 DNS 文件其他剩余的部分,第二步是设置 DNS 服务器为 8.8.X.X。这个同时关注 dev 域和访问外网的请求。如果不添加 8.8.X.XDNS 服务器,当你的 Docker Machine 的网络发生变化,你的 DNS 服务器缓存将自动结束,而且不得不通过重启机器才能转换网络。

这时候你应该能连接到 VM 中正在运行的 Docker 进程。你可以通过正在运行的 Docker Machine dev 环境看到是如何连接的。也可以通过 Docker ps 测试是都有正在运行安装的 Docker,可以看到:

CONTAINER     ID    IMAGE     COMMAND    CREATED

看到这个显示说明你已经正确安装配置环境。

Part3:开始在容器中开发

在容器中开发需要一些思维上的转换,很多时候与我们过去写代码、运行代码、测试等不太一样。你有一个或者多个本地数据库,或者可能模拟 S3 和 DynamoDB 的依赖。

在使用容器开发之前的本地开发,你可能直接就可以关闭本机的 OS 或者虚拟机,或者你可以安装一切你所需要的软件,随着时间的发展,你可以不断增加对系统和程序的配置。也可能是一个 Snowflake Server。可能在管理依赖的开始会存在一些问题,因此像 Bundler、pip、virtualenv 和 RVM 他们可以统一混合使用帮助解决问题。尽管你可以测试出这是一个新版的 MYSQL,但是真正做起来就不是那么简单了。

在容器的范畴中,你没必要拥有一个持久并不断完善的来发环境。你可以做你想做的,但是这并不是推荐的工作流方式。传统上拥有一个能在上面运行代码的“VM”,被取而代之的是创建一个更轻量级可视化的叫做“容器”的层。(了解更多,从 Docker 获取更多说明)

这些容器都是从镜像创建的。镜像的本质是一些基础的只读模板,通过模板可以创建你的应用程序开发环境,如果移动容器,容器内所有相关的东西都将被移动。你也可以总是通过重启相同镜像中的容器,当然这也是经常做的。这也就意味着你可以拥有不同的容器做不同的配置,而且完全不用担心管理混乱。你可以一个上面使用的是 Ruby2 的代码,另一个是 Ruby1.9 的代码。创建容器(基于以前使用的的 Ruby 镜像)仅仅使用一个新的 gem 即可。当你安装 gem 处理容器时,处于其他原因,Rails2 需要依赖安装其他很多 gem,你不得不考虑把 gem 安装在你的系统中。

为了获得更多“官方”的应用程序依赖(node、mysql-client 等),当运行一个存在所有应用程序的容器时,你可以用 Dockerfile 检测你的应用程序库去创建一个已经包含这些程序的镜像。

存在很多依赖的情况下,我们通常分离进程,分割到额外的容器中。例如我们其中一种容器,同时依赖 Mysql、redis 和 S3。我们就把他们同时组合进入我们的容器。通过项目根目录中的 YAML 文件整合进入我们的容器中。例如:

web:
build: .
dockerfile: Dockerfile.development
volumes:
- .:/app
links:
- redis:redis.myapp.dev
- s3:s3.myapp.dev
- mysql:mysql.myapp.dev
command: puma -C config/puma.development.rb
mysql:
image: mysql/mysql-server:5.6
volumes:
- /mnt/sda1/var/lib/mysql:/var/lib/mysql
redis:
image: redis:2.6
s3:
image: bbcnews/fake-s3

通过此设置,很容易就可以看到依赖关联关系。我们已经从这个目录和 Dockerfile .development 文件构建 web services。把这个文件作为容器的存储部分挂上去,给这个目录的代码一定的执行权限就可以运行代码。被分离的 Dockerfile 是最主要的,因为在生产中我们根据此文件编译所有的代码以快照的形式进入容器,所以在开发环境如果我们想更改它,就必须重新编译一次才能实现。我们覆盖了已经在 Dockerfile 中被定义的命令,因此我们能加载开发环境的配置。

当我们开始运行 web 的容器时,Docker 生成会意识到他关联了 redis、mysql、s3 等容器,因此会自动启动他们。除此,Docker 还将 dev 域名和正确的容器写入 /etc/hosts 文件。由于这样操作的话其他的人不需要什么配置,这样就容易很多了。我们可以使用特定版本的 redis 和 mysql。Mysq 装载在虚拟机的一个目录这样使数据可以在扩容器中做到持久化保存。

每一个项目都有一个自定义的 application-level 配置。这个配置文件需要运行在 Dockerfile,其他的服务运行在 docker-compose.yum 文件。例如下面开始一个新项目,在 Dash 中使用 dev 命令:

git clone [GIT_URL]
dev up# (simply an alias to docker-compose up -d)

 

Part 4: Web 浏览器 & 服务间通信

你可能会想,怎么才能看到编译的结果?这又不是本地环境。完全正确,我还没有讲到怎么请求访问到你的容器。

如果你以前一直在操作系统中开发而不是通过虚拟机,你可能经常使用 http://localhost:8000 访问你的 APP。在 Docker 和 Docker Machine 中,现在是两个完全不一样的抽象概念。

然后在发生交换时,增加了分离的概念。容器在孤立我们每一个服务的同时,我们也可以更好的了解什么是真正的服务。

为了达到和本地环境一样的简单级别,我们还必须做点什么才行。

就像我得 Part1 中所说的一样,一个小的 dns 服务器环境,不包括 dnsmasq,Dev TLD 的所有的请求都被路由到 Docker Machine VM。我们只需要怎么把这些请求路由到正确的容器即可。不幸的是,默认的容器网络接口并没有暴露,VM 内部容器的 IP 也是随机分配。你可以绑定主机的端口到容器的端口之上,但是必须仔细处理这之间的冲突。

这就是 Jason Wilder 的 Nginx 反转代理容器所讲的内容。它利用 Docker 内部的原理关注容器的开关,然后通过 nginx 反转代理实现动态配置。它也是通过绑定了 VM 的 80 端口实现。一个新的容器都包含 VIRTUAL_HOST 环境变量,可以将大量的流量路由到容器中。由于能都实时运行,我们就很容易添加两行代码进入 Docker-compose.yml 中:

web:
...
environment:
- VIRTUAL_HOST=myapp.dev # For nginx proxy

停止容器(停止 dev 中的 web),删除容器(删除 dev 中的 web),然后启动所有的备份(dev 环境)。这是另一个集中化环境需要转换思维的例子。首先我停止服务,然后更改新的环境变量,重新开启。因为新的环境变量加入新建的容器,最好的办法是删除容器,并重新开始。

反转代理也解决了对服务开发的问题:怎么定义两个我正在开发的服务之间的依赖关系?

把两个不同的服务定义在两个不同容器的 docker-compose.yml 中可以让他变得更复杂。他们每次构建一个其他容器时,会循环依赖引用另一个容器,从而进入噩梦。然而使用我们的 dnsmasq 容器,每一个 dev 的请求都被路由到 nginx。只要你的服务在 dev TLD 而且注册在虚拟机下,那么每一个请求都能请求所有的链接。我们有自己的开发者入口,可以设置为 ifttt.dev 开发身份,这样就可以请求访问 ifttt.dev 内部的程序。如果你访问的程序不在运行,nginx 会返回 503。

Part5: 使用包管理

对生产代码来说,Dockerfile 一步一步安装相关联的程序包是很有意义的。对于 Ruby 项目来说,我们使用的是 Bundler 做的。我们创建镜像时可以运行 bundler 安装,但是你必须保证包存在情况下才可以运行成功。没必要但是整个 Bundler 打包的过程。

然而在开发环境不同,如果我们每次都安装一个完整的 Bundler 添加一个自己的 gem,那将变得很脆弱。更糟糕的是如果你不包含在 Dockerfile 内,就不得不每次都重新启动一个新的容器!幸运的是,我们有更好的办法解决了这个问题:

web:
...
volumes_from:
- bundler-cache
...
bundler-cache:
image: ruby:2.2
command: /bin/true
volumes:
- /usr/local/bundle

通过创建另一个以 Ruby 为基础的镜像服务作为我们的应用程序(使用 Dockerfile 定义),我们可以利用一些 Docker 内部更深的东西。bundler-cache 容器定义了一个使用 gem 安装到系统路径的存储容量,一运行即可。即使容器没有激活,我们也可以从 bundler-cache 容器挂载在存储上使用。如果删除了这个 web 容器,我们依旧可以保持这个 bundler-cache 容器,等待下次重新创建容器时,直接挂载这个存贮容量后所有的 gem 就存在了。如果你想清除缓存并重启,很容易在开发直接删除 bundler-cache 即可。

对每一个项目都使用包管理的模式,我们发现这是一个非常方便快捷安装管理的方法。不过最大的问题是如果你以外地删除 bundler-cache,你就必须重新安装所有的 gem。

总结

容器化与 Docker 在基础设施层是一个非常不错的工具。如果你计划以后转移到容器上,我非常推荐你在本地开发环境首先尝试。自从内部开始部署 Dash,我们看到新开发员工的管理时间从几天到几小时不等。我们能够在一周之内做到迁移(包括实际更改 Dash 本身),我们的工程师们已经开始对此做出自己的贡献。

原文链接:Developing with Docker at IFTTT(翻译:ylzhang 审校:魏小红)

更多 Docker 相关教程见以下内容

Docker 安装应用(CentOS 6.5_x64) http://www.linuxidc.com/Linux/2014-07/104595.htm 

Ubuntu 14.04 安装 Docker  http://www.linuxidc.com/linux/2014-08/105656.htm 

Ubuntu 使用 VNC 运行基于 Docker 的桌面系统  http://www.linuxidc.com/Linux/2015-08/121170.htm

阿里云 CentOS 6.5 模板上安装 Docker http://www.linuxidc.com/Linux/2014-11/109107.htm 

Ubuntu 15.04 下安装 Docker  http://www.linuxidc.com/Linux/2015-07/120444.htm 

在 Ubuntu Trusty 14.04 (LTS) (64-bit)安装 Docker http://www.linuxidc.com/Linux/2014-10/108184.htm 

在 Ubuntu 15.04 上如何安装 Docker 及基本用法 http://www.linuxidc.com/Linux/2015-09/122885.htm

Docker 的详细介绍:请点这里
Docker 的下载地址:请点这里

本文永久更新链接地址:http://www.linuxidc.com/Linux/2015-10/124219.htm

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-21发表,共计7117字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中