亚马逊AWS官方博客

通过 AWS 上的服务器端 Swift 持续交付

Swift 是 Apple 于 2014 年发布的一种通用编程语言,旨在通过一个可提高开发人员工作效率和代码安全性的程序包,提供 Objective-C 的众多功能,例如类型安全、后期绑定和动态调度。尽管 Swift 已成为 iOS 开发中一种可替代 Objective-C 的流行语言,但它也引起了人们对服务器端开发的兴趣。Swift 兼顾类型安全和开发人员工作效率,因而可替代其他流行的服务器端编程语言,对于已经在用其进行 iOS 开发的开发团队而言,Swift 尤其有趣。

快速、灵活的持续集成和交付管道对于确保开发人员就其代码中潜在问题获得快速反馈,以及保持尽快交付至关重要。好的 CI/CD 管道可以很好的完善 Swift 提供的工作效率和安全性;该编程语言可在代码级别确保了上述优势,而 CI/CD 管道可在集成和部署过程中优化质量和速度。

本博文说明了如何使用 Amazon Web Services (AWS) 为服务器端 Swift 代码创建一个持续交付管道。我们将使用托管式 AWS 服务(例如 AWS CodePipelineAWS CodeBuildAWS CodeDeploy)创建管道。该管道将在两个计算平台上编译、构建、测试和部署简单的 Swift Web 服务:直接在运行 Ubuntu 的 EC2 虚拟机上、以及在托管式容器平台 AWS Fargate 上。

管道架构概述

测试、构建和部署 Swift Web 服务的管道体系架构图。

该管道旨在测试、构建和部署我们的 Swift Web 服务。在开始之前,让我们退后一步,考虑一下我们希望该管道具有哪些属性。

  • 快速:此管道应无需开发人员输入即可更改,并尽快执行构建、测试和部署过程。
  • 灵活:技术和业务需求不断变化。通过我们的架构,创建其他管道步骤或修改现有步骤应变得简单、快速且无中断。
  • 弹性:我们希望确保在不牺牲备用容量的情况下,实现快速管道的目标。我们希望随时获得电能,但不想在夜晚和周末为付电费。

我们对如何使用 CodePipeline、CodeBuild、CodeDeploy 和其他 AWS 服务,来构建此管道以实现这些目标持坚定的看法;但是,这不是非全即无的情况。CodePipeline 支持通过多种服务来构建、测试和部署代码,并且 CodePipeline 本身可以与其他服务(例如 Jenkins,Concourse CI 等)交换。如果您想了解 Jenkins 与CodePipeline 的集成,请参阅创建四阶段管道教程。

我们鼓励您将此设置作为您自己的管道的基础,以了解组件如何组合在一起。请随时根据您的具体情况对其进行修改。

让我们了解一下设置中的每个组件。

CodePipeline – CI/CD 管道

首先,让我们检查管道本身。AWS CodePipeline 是一项完全托管的持续交付服务,使我们可以将管道的源、测试、构建和部署步骤结合在一起,同时与其他 AWS 服务集成,并支持最小特权访问。

CodePipeline 会整理应在阶段内执行的操作。我们的管道将包括四个阶段:

  1. 测试
  2. 构建
  3. 部署

源阶段将响应关联的 Swift Git 存储库中的更改。测试阶段将对代码运行单元测试。构建阶段将并行运行两项操作:一个用于构建 Swift 二进制文件,另一个用于构建 Docker 容器。最后,部署阶段也将并行运行两项操作,将 Swift 二进制文件部署到 EC2,将 Docker 容器部署到 ECS。

CodeCommit – 源代码

我们管道的第一阶段是源阶段,负责管理与源代码存储库的连接,并在有更新时提取最新的代码修订版。在我们的管道中,我们使用了 CodeCommit,一个由 AWS 托管的 Git 源代码存储库。CodePipeline 支持其他源代码存储库,例如 GitHub、GitHub Enterprise 甚至 S3 存储桶。

S3 –构件存储库

我们的管道将需要在管道中创建和使用构件,例如来自 Git 存储库的源代码,在 CodePipeline 构建阶段编译的 Swift 二进制文件等等。CodePipeline 将使用一个特殊构件存储桶来存储这些构件,但将会存储在 ECR 容器注册表中的 Docker 镜像除外。

ECR – 容器存储库

Amazon Elastic Container Registry (ECR) 是完全托管的 Docker 注册表,有两种使用方式:存储生成的 Swift Docker 镜像,以及存储用于编译 Swift 二进制文件的自定义 CodeBuild 镜像。

ECR 的第一个用例是使 CodeBuild 能够创建已编译的 Swift 二进制文件。稍后,我们将了解管道如何使用 AWS CodeBuild 为 Swift 应用程序创建二进制文件,但是现在需要了解的重要一点是,CodeBuild 允许我们在使用 Docker 镜像(已预先安装所需构建设置)的基础环境上,运行一组构建命令。在本例中,我们使用 Swift 工具链和库创建了一个自定义 Docker 镜像,以便 CodeBuild 可以编译 Swift 二进制文件。

ECR 的第二个用例是充当我们的管道创建的 Swift 应用程序容器镜像的构件存储库。管道将使用此存储库放置镜像,当部署新的应用程序版本时,ECS 将使用此存储库提取镜像。

ECS 和 Fargate – 容器编排和托管

我们为代码设置了两个目标,显示了如何直接在 EC2 实例上以及通过 Docker 容器托管 Swift 应用程序。在实际应用中,您很可能需要将您的服务部署到一个或另一个平台上,不过此管道对两种方法都做了演示。

我们提供了托管 Docker 容器的多种选择:我们可以部署到在 EC2 实例上运行的自托管 Kubernetes 集群,也可以使用 Amazon Elastic Kubernetes Service (EKS)来利用 AWS 托管的 Kubernetes 服务。但是,在此管道中,我们将使用AWS Elastic Container Service (ECS)。ECS 与 AWS CodePipeline 和其他 AWS 服务的集成性很强,从而使设置和部署过程变得简单方便。我们将 ECS 与 AWS Fargate 耦合,AWS Fargate 是与 ECS 配合使用的无服务器计算引擎,无需预置和管理服务器。

我们的 ECS 平台与 AWS Application Load Balancer (ALB) 集成在一起,后者充当传入请求的入口点,并在 Fargate 上运行的容器之间分配这些请求。

EC2 – 虚拟机托管

我们要在此管道中设置的第二个托管平台将直接在 AWS EC2 实例上托管 Swift 应用程序。EC2 实例在自动扩展组中运行,确保我们根据传入需求配置或删除 EC2 实例,并删除所有标记为运行状况不佳的 EC2 实例。

与 ECS 平台类似,我们正在运行连接到 Application Load Balancer (ALB) 的这些实例,以将流量分配给所有运行状况良好的 EC2 实例。

CodeBuild – 测试和构建

我们将使用 CodeBuild 来测试和构建我们的 Swift 代码。CodeBuild 以经优化 Docker 镜像的形式,提供预先打包的构建环境,这些镜像经过设置,可提供用各种编程语言构建和测试代码所需的工具链。在本例中,我们还将利用在 CodeBuild 中创建自定义构建环境的功能,来测试和编译 Swift 应用程序。

我们还提供了一个 buildspec 文件,用以告诉 CodeBuild 每个构建过程要执行哪些步骤。这是一个 yaml 文件,描述了构建过程每个阶段(例如,安装所有必备工具或库的安装阶段,构建阶段,用于打包构件的构建后阶段)所需的命令,以及应存储构建过程的哪些构件供后续步骤使用。可以将 buildspec 文件指定为 CodeBuild 项目定义的一部分,也可以将其包含为项目源代码的一部分。在本例中,我们将采用后一种方法,在示例应用程序中使用 buildspec.*.yml 文件

CodeBuild 将以三种不同的方式用于我们的管道:

  1. 编译 Swift 代码并将生成的二进制文件推送到 S3 构件存储桶的构建环境。所用 buildspec 文件是项目源目录中的 buildspec.ec2.yml
  2. 在 Docker 镜像内编译 Swift 代码,然后使用多阶段构建来创建可部署镜像的构建环境。可部署镜像部署到 ECR 存储库。所用 buildspec 文件是项目源目录中的 buildspec.docker.yml
  3. 运行 Swift 应用程序的单元测试的测试环境。所用 buildspec 文件是项目源目录中 buildspec.test.yml

CodeDeploy – 部署到实时系统

我们的管道需要部署到两个不同的系统:ECS 用于 Docker 可部署镜像,EC2 用于 Swift 二进制文件。

CodePipeline 具有与 ECS 的内置集成,以支持滚动部署或蓝/绿部署。在 EC2 上进行部署需要其他规则和指导;我们需要提供有关如何从 S3 构件存储库获取新的二进制文件,以及如何在 EC2 实例上执行部署或回滚操作的说明。

这时 CodeDeploy 可帮助我们。借助 CodeDeploy,我们可以提供有关在 EC2 实例上部署的每个阶段应采取哪些步骤的说明,尤其是:

  • 安装前
  • 安装后
  • 应用程序启动
  • 应用程序停止
  • 验证服务
  • 排除流量前
  • 排除流量后
  • 允许流量前
  • 允许流量后

有关如何使用每个阶段的详细信息,请参阅 AWS CodeDeploy 文档中有关EC2/本地部署的 AppSpec “挂钩”部分

虽然我们不需要为每个部署阶段提供脚本,但我们可以通过部署随附的 AppSpec 文件提供脚本。在此示例中,我们利用ApplicationStopApplicationStart 来停止和启动 systemd 服务,并利用BeforeInstallAfterInstall 来确保我们具有正确的目录结构和权限。

CodeDeploy 还允许我们指定各种部署配置,例如一次性直接替换部署一个实例,一次性直接替换部署一半的实例,蓝色/绿色样式部署或您自己的自定义配置。CodeDeploy 会监控部署,提供成功或失败通知,并在部署失败时回滚。

CloudFormation – 更新和维护管道

正如我们将在下一节中了解的,我们正在使用 CloudFormation 来设置和配置管道中的所有 AWS 资源、EC2 和 ECS 组件以及所有联网组件和安全组件。CloudFormation 使许我们可以重复进行自动化设置和配置,并在代码更改时跟踪基础设施的更新。

入门

让我们开始构建管道。上一部分中描述的 AWS 资源和所需的配置将通过 CloudFormation 脚本进行配置。

设置一个 AWS 账户、CloudFormation 和 Docker

第一步,确定将要预置资源的 AWS 账户。如果您已经预置要使用的帐户,则可以跳过此步骤。否则,请访问https://portal.aws.amazon.com/billing/signup 并创建一个新帐户。

用于创建 AWS 账户的字段屏幕截图

请注意,CloudFormation 脚本将预置将会生成每月账单的资源使用完这些资源后,请确保销毁 CloudFormation 堆栈以删除这些资源。有关如何删除此类资源的说明,请参见本文末尾的“清理”部分。

要从 CLI 运行 CloudFormation 创建脚本,您需要一名有 CLI 访问权限的用户。从 AWS 控制台,导航到 IAM 服务,然后单击添加用户。选择一个用户名,并确保已选择编程访问。接下来,直接添加现有策略,然后选择管理员访问权限。确保复制或下载访问密钥 ID秘密访问密钥

如果您尚未安装 AWS CLI,请立即安装。下载链接和说明位于 AWS 命令行界面。确保已使用上一步中记下的访问密钥 ID秘密访问密钥 配置 CLI。

创建一个新的 S3 存储桶,该存储桶将存储打包的 CloudFormation 模板。如果您的现有帐户带有一个存储桶,则可以使用该存储桶。运行脚本的用户应对此存储桶具有读/写权限,并且该存储桶应位于您要预置资源的同一帐户中。

我们将在后续步骤中创建并上传自定义 Docker 镜像,其中包含供 CodeBuild 使用的 Swift 工具链。确保已在工作站上安装和配置 Docker

为了将代码从工作站推送到 Git,我们将使用 Git CLI。确保您的工作站上可以正常安装。如果您尚未安装 Git,则可以从 Git 下载中获得可用于多个操作系统的 Git。

下载并配置 CloudFormation 安装程序包

CloudFormation 模板、帮助程序脚本、示例应用程序和 CodeBuild Swift 构建镜像捆绑在一起,位于 GitHub 上的一个文件中。(您也可以浏览 GitHub 页面并从以下位置克隆它:https://github.com/aws-samples/aws-pipeline-server-side-swift-blog。) 下载该文件并解压缩内容。更新 package.json,以使带有 CF_BUCKET= 的两行后跟您在上一步中配置的CloudFormation S3 存储桶的名称。

create-stack.json 中,您可以选择更新将要创建的 CloudFormation 堆栈的名称;名称默认为 swift-build。您还可以更改将应用于所有已创建资源的标签。

部署 CloudFormation 模板

现在,我们可以部署 CloudFormation 模板。./scripts/create-stack.sh 中有一个帮助程序脚本,它将打包并运行 CloudFormation 脚本。您可以自己运行:

CF_BUCKET=<your CloudFormation S3 bucket name here> ./scripts/create-stack.sh

输出代码的屏幕截图
如果您恰好安装了 npm,则还有一个方便的快捷方式:npm run-script create 将为您运行相同的命令。

现在,我们需要等待脚本完成,这大约需要 15-20 分钟。您可以通过查看 CloudFormation 部分中的 AWS控制台或运行 aws cloudformation wait stack-create-complete 来查看状态。

注意:CloudFormation 脚本会创建名为 swift-appcodebuild/swift的 ECR 存储库。如果您在尝试创建这些存储库时遇到 CloudFormation 错误,请检查现有存储库是否未使用这些名称。

创建 CodeBuild 自定义构建运行时

在此步骤中,我们将创建自定义 CodeBuild 运行时镜像,该镜像将在 CodeBuild 编译 Swift 二进制文件时使用。

首先,让我们创建 Docker 镜像。在 AWS 控制台中,导航至 ECR,单击 codebuild/swift,然后按照 codebuild-image 文件夹中查看推送命令命令中的说明操作。系统将要求您登录 ECR,构建镜像,然后对其进行标记,以使其关联 ECR 存储库,最后将新创建的镜像推送到 AWS。步骤如下所示(存储库名称将根据您所在的区域帐户而有所不同):

cd codebuild-image
$(aws ecr get-login --no-include-email --region us-west-2)
docker build -t codebuild/swift .
docker tag codebuild/swift:latest 123456789123.dkr.ecr.us-west-2.amazonaws.com/codebuild/swift:latest
docker push 123456789123.dkr.ecr.us-west-2.amazonaws.com/codebuild/swift:latest

推送 Swift 应用程序代码

现在,管道已安装正确的 CodeBuild 自定义运行时,一切就绪,可以推送应用程序代码了。

app 文件夹中有一个示例 Swift Web 应用程序,我们将用其测试管道。从项目根目录导航到 ./app 目录,并初始化一个 Git 存储库:cd app && git init

在使用 Git CLI 将 Web 应用程序代码推送到 CodeCommit 之前,您可能必须配置 AWS 和 Git 才能启用访问。AWS 文档中的设置AWS CodeCommit描述了两种方法,涉及 HTTP 访问或 SSH 访问。两种访问方法都可以正常运作;尽管以下示例中使用了 SSH 访问,但是两种情况下,安装完成后的步骤相同。

导航至 AWS 控制台中的 CodeCommit 服务,然后找到由 CloudFormation 脚本创建的存储库(该存储库应以您使用的堆栈名称开头,例如 swift-build-...)。单击该存储库,然后按照说明,使用应用程序 git 存储库的内容初始化 CodeCommit。

存储库视图的屏幕截图

git remote add origin ssh://git-codecommit.us-west-2.amazonaws.com/v1/repos/swift-build-Pipeline-1234567890ABC-Repo
git add .
git commit -am "initial commit"
git push --set-upstream origin master

确认管道执行

现在,CodePipeline 应选择 CodeCommit 存储库中更改,然后启动管道。让我们确认一下。

导航至 CodePipeline。单击名称以 CloudFormation 堆栈名称开头的管道。git 推送几秒钟后,它应该选择 CodeCommit 存储库中更改,然后启动。10-15 分钟后,它应该完成所有四个阶段:源、测试、构建和发布。

显示 CodeCommit 存储库中更改和启动的屏幕截图

完成后,我们需要对 CloudFormation 堆栈做稍许调整。最初,当我们部署堆栈时,我们将所需的 ECS 任务数设置为零。这是为了防止 CloudFormation 在尝试使用尚不存在的 Swift docker 镜像时无法稳定。现在,管道已经执行并创建了该Docker 镜像,我们可以修改 CloudFormation 参数,以包括一个或多个 ECS 任务。

create-stack.json 中找到 ECSDesiredCount 参数,该参数当前应设置为 0。将其修改为 2,并运行 npm run-script update 或:

CF_BUCKET=<your CloudFormation S3 bucket name here> ./scripts/update-stack.sh

成功更新 CloudFormation 堆栈后,确保已将应用程序正确部署到 EC2 服务器。导航至 EC2 AWS 控制台,然后单击侧栏,找到负载均衡器部分。首先单击名称以 swift-EC2 开头的负载均衡器。复制 DNS 名称,并将其粘贴到浏览器窗口中。确认其响应“It works!”

您也可以通过以下命令行验证 EC2 服务是否正常运行:

curl $(aws cloudformation describe-stacks --stack-name swift-build --query "Stacks[0].Outputs[?OutputKey=='Ec2LbUrl'].OutputValue" --output text)

显示 EC2 服务正在通过命令行运行的屏幕截图

最后,确保在 Docker 镜像上加载的应用程序也已正确部署到 ECS 服务。导航至 EC2 AWS 控制台,然后单击侧栏,找到负载均衡器部分。首先单击名称以 swift-ECS 开头的负载均衡器。复制 DNS 名称,并将其粘贴到浏览器窗口中。确认其响应“It works!”

与 EC2 一样,您也可以通过以下命令行验证 ECS 服务是否正常运行:

curl $(aws cloudformation describe-stacks --stack-name swift-build --query "Stacks[0].Outputs[?OutputKey=='EcsLbUrl'].OutputValue" --output text)

推送代码更改

最后,快速更改应用程序源代码,并确保它正确更新了服务。

更新 swift-codebuild-app 存储库。编辑 Sources/App/routes.swift,并将 return "It works!" 修改为 read return "It works! With an update!" 或您喜欢的任何已更新消息。提交这些更改,并将其推送到 CodeCommit 存储库:

git add .
git commit -m "Changed application message"
git push

现在,等待管道更新,并确认两个负载均衡器已使用正确的消息更新。使用与上一步相同的 DNS 地址,检查更新是否已部署到 EC2 服务器和 ECS 服务。

清理

一旦您完成了管道尝试,不再使用管道,请确保销毁 CloudFormation 堆栈,以节省每月未使用资源的成本。

在销毁 CloudFormation 堆栈之前,我们需要确保 S3 存储桶和 ECR 存储库为空。您可以通过 AWS 管理控制台或以下命令行手动执行此操作:

aws s3 rm s3://$(aws cloudformation describe-stacks --stack-name swift-build --query "Stacks[0].Outputs[?OutputKey=='S3ArtifactBucket'].OutputValue" --output text)/ --recursive
IMAGES_TO_DELETE=$(aws ecr list-images --repository-name codebuild/swift --query 'imageIds[*]' --output json)
aws ecr batch-delete-image --repository-name codebuild/swift --image-ids "$IMAGES_TO_DELETE"
IMAGES_TO_DELETE=$(aws ecr list-images --repository-name swift-app --query 'imageIds[*]' --output json)
aws ecr batch-delete-image --repository-name swift-app --image-ids "$IMAGES_TO_DELETE"

一旦存储库和存储桶为空,请通过 AWS 管理控制台或以下命令行销毁 CloudFormation 堆栈:aws cloudformation delete-stack --stack-name swift-build

问题排查诀窍

希望您能够成功部署管道。如果您确实遇到麻烦,以下是可能会遇到的几个潜在问题,以及如何解决这些问题的提示。

名称冲突

CloudFormation 脚本为大多数 AWS 资源使用动态名称,但是在几个示例中,管道假设没有其他资源在使用给定名称。具体来说,CloudFormation 脚本为用于提供 Swift 工具链的 Docker 镜像创建一个名为 codebuild/swift 的存储库名称。如果您从 CloudFormation 收到错误消息,表明无法创建该名称的 ECR 存储库,请检查是否存在名称冲突。

ECS ARN 格式

根据您创建 AWS 账户的时间以及账户设置,您可能需要为 ECS 服务启用长 ARN 格式。如果收到类似于无法在资源 AWS::ECS::Service 上添加标签的错误。必须启用新的 ARN 和资源 ID 格式才能将标签添加到服务 CloudFormation 中,您可以通过运行以下命令为 CLI 用户启用长 ARN 格式:

aws ecs put-account-setting --name containerInstanceLongArnFormat --value enabled
aws ecs put-account-setting --name taskLongArnFormat --value enabled
aws ecs put-account-setting --name serviceLongArnFormat --value enabled

进一步优化和后续步骤

有几种方法优化管道的方法。

更复杂的管道可能需要转储环境。我们可以将其轻松地添加到管道中,以便其首先部署到访问受限环境中,从而可以在部署到生产站点之前,执行人动审核或一组自动化测试。该管道可能如下所示:

显示源代码、单元测试、测试版部署、集成测试、人工批准检查点和生产部署的管道图示

另一个改进是可生成更有用的报告。目前,第二阶段的管道运行单元测试,但是开发人员获取其代码反馈的唯一方法是查看 CodeBuild 记录的输出。好消息是,CodeBuild 能够从测试报告中收集信息,并将该报告关联 CodeBuild 执行。有关更多信息,请参阅在 CodeBuild 中使用测试报告

最后,一个好的管道将检查各种质量指标,而不仅仅是单元测试和集成测试所测试的功能。借助 CodePipeline,您可以将性能、安全性、负载测试等集成到管道中。方法之一,是执行与本文类似的操作,并利用 CodeBuild 托管您的测试脚本,但是CodePipeline 还允许您将其与BlazeMeter、Ghost Inspector、Micro Focus StormRunner、Runscope API 或现有的 Jenkins 工具集成。一旦管道成熟,就可以取消人工批准,或者仅将其用于高度敏感的模块。具有这些附加检查的完整管道可能如下所示:

包括静态安全扫描和代码质量扫描(低于单元测试)以及性能测试和实时安全测试(低于集成测试)的管道图示

祝您在使用服务器端 Swift 进行开发的过程中一切顺利! 我希望这篇文章可以帮助您顺利开始在 AWS 上构建、测试和部署 Swift 代码,并鼓励您尝试使用提供的 CloudFormation 代码。