如何使用 Docker、ECS、Terraform 重建基础架构?
早期 Segment 基础架构普遍组合在一起。我们通过 AWS 界面设定实例,使用许多闲散的 AMI,并且采用三种不同的部署方式。
然而随着商业的飞速发展,工程师团队的规模不断扩大,基础架构的复杂度也不断提高。提高生产效率的方法仍旧只在一小部分人中间传播,虽然生产效率在不断提高,但是如果想一直保持高速增长,还要全面修整基础架构。
因此,几个月前,我的团队一起讨论:「如果今天重新设计基础架构,会是怎样一种结构?」。
10个星期后,我们彻底重构了基础架构。我们放弃了几乎所有实例与旧的配置,将我们的服务转移到 Docker 容器中运行,并且转而使用全新的 AWS 账号。
在此我们花了很长时间思考如何将产品架构变得简单、易用且可审计,同时保留扩展的灵活性。
以下是我们的解决方法。
使用不同的 AWS 账号
我们并不使用 Region 和 Tag 来区分不同的阶段如预发布环境和生产环境,而是使用完全不同的 AWS 账号 。我们必须保证设定脚本不会影响正在运行的服务。同时,新的账号就像白纸一样,可以重新开始。
此处, ops 账号作为跳跃点和集中登陆点。公司内的每个人有一个 IAM 账号用于登陆。
其他环境有一组 IAM 角色可以相互切换。这意味着,管理账号只有一个登陆点,也只有一个位置限制访问。
比如:Alice 可能拥有所有三个环境的访问权,但 Bob 只能访问 dev(如果他删除生产负载均衡器的话)。但是,他俩都可以进入 ops 账号。
现在,不需要复杂的 IAM 设定以限制访问权限,我们可以通过环境查看用户通 role 进行分组。在界面使用各个账户就像切换当前活跃用户一样简单。
我们无偿地实现了真正的隔离,不需额外的配置,无须担心预发布环境的安全性,或它对生产环境数据库的改动。
能够共享配置代码的另一个好处是现在的 预发布环境成为了一个镜像 。在配置上唯一的不同是实例的大小和容器的数量。
最后,我们也启用了各个账户之间统一计费。每个月我们用同一张发票付费,同时能按照环境查看费用明细。
Docker 与 ECS
账户设定完毕后,就该设置服务的运行方式了。为此,我们使用了 Docker 与 EC2 容器服务(ECS) 。
现如今,我们大多数的服务都运行在 Docker 容器内,包括 API 与数据管道。容器每秒钟接受成千上万次请求,每个月处理500亿事件。
Docker 的最大好处在于它使团队能够从零开始搭建服务。我们不再有一套复杂的设定脚本或 AMI ,我们只要给生产集群提供一张镜像就行了。无需状态性的实例,我们能保证在预发布环境和生产环境运行一模一样的代码。
设定服务在容器中运行后,我们选择 ECS 为调度器。
在一个高水平上,ECS 实际负责在生产环境下运行容器。它负责调度服务、将它们置于不同的主机中,在与 ELB 关联时零宕机重载。它甚至可以跨多个 AZs,从而达到更佳可用性。如果一个容器宕机了,ECS 会确保该容器在集群中的新实例上重启。
切换到 ECS 之后,极大地简化了运行服务的过程,无需再担心启动任务或设定实例。因为它很简单,只需要添加一个 Dockerfile,设定 task,再将其与集群关联即可。
在我们的配置中,Docker 镜像由 CI(持续集成) 构建,之后推送到 Docker Hub。当某项服务启动时,它从 Docker Hub 获取镜像,之后 ECS 在各个机器间调度之。
我们依照集群涉及的组件与负载间档对他们分组(不同的集群用于不同的 API、CDN、App 等)。不同的集群意味着我们可见性更高,能为其配置不同的实例类型( ECS 没有实例关联度的概念)。
每项服务都包含一个特别的任务用于指明容器版本,运行的实例数量,以及该选择的集群类型。
在运行时,服务会自行注册 ELB,同时使用健康检查确定容器是否可以运行。我们在 ELB 指定一个本地的 Route53,因此各个服务能相互通信,通过 DNS 就能相互引用。
因为不需要 任何 服务搜索,所以设置非常顺利。本地的 DNS 帮助我们记录一切。
ECS 运行所有服务,我们通过 ELB 就能获取免费的云监控测量数据。这比在启动时就要在中央注册服务要简单的多。而且,更棒的是,我们中央不需要再面对状态冲突了。
使用 Terraform 模板
Docker 与 ECS 负责实现运行每一项服务,Terraform 是将他们联合在一起的胶水。在高水平上,一系列脚本负责创建并更新基础架构。你可以将其想作一个 Cloudformaition 模版,除了它不会让你想自戳双目。
现在,无需运行一系列服务器以维护状态,只需一些脚本用来描述集群。配置在本地运行(未来通过 CI 运行),提交到 git 上。因此,我们能得到一系列记录,能够了解生产环境中基础构架的实际情况。
以下是我们的 Terraform 模板中设置 bastion 结点的样本代码。该代码创建所有的安全组,实例和 AMI,因此我们可以简单地为未来的环境设定跳跃点。
// Use the Ubuntu AMI module "ami" { source = "github.com/terraform-community-modules/tf_aws_ubuntu_ami/ebs" region = "us-west-2" distribution = "trusty" instance_type = "${var.instance_type}" } // Set up a security group to the bastion resource "aws_security_group" "bastion" { name = "bastion" description = "Allows ssh from the world" vpc_id = "${var.vpc_id}" ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags { Name = "bastion" } } // Add our instance description resource "aws_instance" "bastion" { ami = "${module.ami.ami_id}" source_dest_check = false instance_type = "${var.instance_type}" subnet_id = "${var.subnet_id}" key_name = "${var.key_name}" security_groups = ["${aws_security_group.bastion.id}"] tags { Name = "bastion-01" Environment = "${var.environment}" } } // Setup our elastic ip resource "aws_eip" "bastion" { instance = "${aws_instance.bastion.id}" vpc = true }
我们在预发布环境与生产环境中使用同样的模板设置单个 bastion。唯一需要修改的是 IAM 键。
修改也非常简单,不需要修改整个基础架构,Terraform 会在需要的时候更新内容。
当我们要将 ELB 超时时间改为60秒时,只需通过 terraform apply 简单地查找/替换就好。两分钟之后,我们的 ELB 生产环境就完全改变了。
而且,这是可复制、可审计且自文档化的,每一步都是可见的白盒操作。
我们将所有配置都放在一个中心的 infrastructure 库,这样很容易看清某一项给定的服务是如何配置的。
到目前为止,我们还未讲到核心部分。我们希望利用模板转变更多的 Terraform 配置,联合独立文件从而减少共享文件的数量。
同时,我们也发现了有关 .tfstate 的一些陷阱。Terraform 总是先从现存的基础架构读取数据,如果状态不同步就会报错。最后,我们将 .tfstate 放到库中。我们希望能通过 Atlas 或使用 CI 来解决该问题。
使用 Datadog 监控
至此,我们已经搭好了基础架构,开通了服务,做好了必要的隔离。最后要做的是监控生产环境中所有运行程序。
在新环境中,我们将所有的监控和指标都用 Datadog 进行。不夸张的说 Datadog 真的棒极了!
我们对 Datadog 的界面,API 以及其和 AWS 的整合都非常满意。但是,想要完全利用这个工具,还需要一些关键的设置。
首先,我们与 AWS 和 Cloudtrail 进行整合。这能使我们全面了解环境中的所有情况。由于我们已经与 ECS 整合过了,Datadog feed 会在任务内容更新时自动更新,于是我们能在部署放生改变时得到免费通知。搜索 feed 的过程也异常快捷,能轻松找到最近一次的服务部署或重新调度。
接下来,我们确保将 Datadog-agent 作为 AMI 基础容器(datadog/docker-dd-agent)。它不仅会从主机( CPU,内存等)收集测量数据,也作为存储 statsd 测量数据的容器。每一项服务都会收集关于查询、潜伏、错误的自定义指标,因此我们可以在 Datadog 中进行探索,获得警告等。我们的 go 工具箱(很快就会开源)会自动收集并在 ticker 输出 pprof ,因此达到监控内存与 goroutines 的目的。
更酷的是,该探针能够图形化展示环境中多个主机间的实例利用情况,因此我们能从更高角度了解可能出问题的实例或集群:
此外,我的队友 Vince 写了一个针对 Datadog 的 Terraform ,因此我们可以针对实际的生产配置设置报警脚本。我们的报警会被记录,同时与生产环境中运行的程序保持同步。
按照惯例,我们会设定两种报警级别: 预警 与 重要警告 。 预警 使线上的工程师了解任何可疑的问题,会在潜在问题发生以前发出。 重要警告 则是会在半夜把你喊起来的严重系统宕机。
此外,当我们完成向 Terraform 模块的转移,将 Datadog 提供程序加到服务描述层之后,所有的服务都会免费获得告警。这些数据都是由内部工具箱与 Cloudwatch 指标驱动的。
让好时光在 Docker 中继续
当所有的组件都准备就绪后,切换的日子终于到来了。
首先,我们会在新的生产环境与原有环境间建立 VPC 对等连接 ——从而集群化数据库并在两者间进行复制。
其次,我们预热新环境中的 ELB 使之能够承受新的负载。亚马逊无法提供自动更改大小的 ELB,因此我们不得不提前为其扩容以应对增加后的负载。
之后,我们只需使用加权的 Route53 路由平稳地将流量从旧环境导向新环境,并且持续监控确保一切正常。
现在,我们的 API 每秒处理成千上万次请求,并且完全运行在 Docker 容器内。
但是还没完,我们还在优化服务的创建方式,减少引用,使得团队中的任何人都能简便地创建服务,同时包含适度的监控与预警系统。此外,我们还想优化容器周边的工具,因为现在的服务已不再围绕实例进行了。
我们还会关注这一领域颇有发展的技术。 Convox 团队真正创建围绕 AWS 基础架构的强大工具。尽管我们很喜欢 ECS 的简单与集成,但 Kubernetes 、 Mesosphere 、 Nomad 与 Fleet 这些都是非常不错的调度器。我们很期待看到他们的后续发展,并会考虑选择一二进行使用。
经历所有这些编排变化之后,我们比以前更加相信将基础构架外包至 AWS 的策略。他们产品化了许多核心服务,完全改变了游戏规则,而且维持了一个极具竞争力的价格。这使得越来越多的初创企业能够高效、低成本低开发产品,同时在维护上节省时间。我们相当看好这些建立在基础生态系统之上的工具。
原文 Rebuilding Our Infrastructure with Docker, ECS, and Terraform 作者 Calvin French-Owen,本文由OneAPM 工程师翻译整理。
OneAPM 能够帮你查看Python 应用程序的方方面面,不仅能够监控终端的用户体验,还能监控服务器性能,同时还支持追踪数据库、第三方 API 和 Web 服务器的各种问题。想阅读更多技术文章,请访问OneAPM 官方技术博客。