亚马逊AWS官方博客

如何使用AWS CodePipeline,AWS CodeBuild与AWS CloudFormation实现Amazon ECS上的持续集成持续部署解决方案

作者:郭威

1. 前述

通过本文章,您将了解如何通过AWS CodePipeline,AWS CodeBuild,AWS CloudFormation 来实现基于Amazon ECS的持续集成持续部署方案。

开发人员在GitHub中提交的新版本代码,会自动触发代码获取,打包镜像,上传镜像仓库,更新新版本容器服务,注册到负载均衡器等操作。

方案中会涉及使用如下组件:

GitHub:示例使用的源,一个提交到GitHub上的PHP示例网站。AWS CodePipeline支持GitHub, AWS CodeCommit服务,或者S3作为源。此次实例使用的Demo软件工程可以从以下链接Fork:

https://github.com/awslabs/ecs-demo-php-simple-app

Docker:作为发布服务使用的容器。演示方案的Build阶段会使用AWS CodeBuild托管的ubuntu/docker 1.12.1基础镜像。

Amazon EC2:作为ECS的容器宿主机集群。

Amazon VPC:服务所在的网络。

Amazon ECS:AWS托管的容器编排服务。文档链接 http://docs.thinkwithwp.com/zh_cn/AmazonECS/latest/developerguide/Welcome.html

Amazon ECR:AWS 托管的容器镜像仓库。文档链接 http://docs.thinkwithwp.com/zh_cn/AmazonECR/latest/userguide/what-is-ecr.html

AWS CodePipeline:AWS 托管的持续集成持续交付服务,可以快速可靠的更新应用程序和服务,集成支持GitHub,Jenkins等主流开源工具。文档链接 http://docs.thinkwithwp.com/zh_cn/codepipeline/latest/userguide/welcome.html

AWS CodeBuild:AWS 托管的构建服务,用于打包代码生成可部署的软件包。文档链接 http://docs.thinkwithwp.com/zh_cn/codebuild/latest/userguide/welcome.html

AWS CloudFormation:批量创建和管理AWS资源的自动化脚本。文档链接 http://docs.thinkwithwp.com/zh_cn/AWSCloudFormation/latest/UserGuide/Welcome.html

2.方案架构

流程如下:

  1. 开发者将一个新版本的代码工程提交到GitHub
  2. Pipeline的Source阶段,检测到指定GitHub的repo有新版本的更新,从GitHub上拉取代码工程,开启已设定好的CICD Pipeline
  3. Pipeline的Build阶段,AWS CodeBuild将新版本的代码工程打包为Docker镜像
  4. AWS CodeBuild将打包好的镜像推送到Amazon ECR
  5. Pipeline的Deploy阶段,AWS CodePipeline触发AWS CloudFormation,其定义了Amazon ECS的Task definition和service
  6. AWS CloudFormation创建新版本的Task definition关联到新版本的Docker镜像,并更新Service
  7. Amazon ECS从Amazon ECR中取到新版本的Docker镜像,并运行来替换旧Task以完成服务的更新部署

3. 搭建

搭建部分分为以下几个步骤:基础设施,与CICD Pipeline的搭建。

3.1   基础设施部分的搭建

这里需要准备好网络,负载均衡器,S3以及运行ECS所需要的宿主机集群。

3.1.1 网络搭建

创建VPC,子网,Internet Gateway,路由表。将Internet Gateway Attach到VPC上,路由表配置0.0.0.0/0指向Internet Gateway,并关联子网。

之后的EC2宿主机集群,负载均衡器等都使用在这个网络里。

3.1.2 负载均衡器

创建ALB应用负载均衡器,监听80端口

选择对应的子网

新建安全组,端口80,并新建目标组

注册目标此时不选择,ECS创建服务时会注册集群和对应端口进来。

下一步审核后创建。

3.1.3 ECS宿主机集群

在ECS的界面下,创建集群

实例配置保持默认或根据情况自行选择,示例中保持默认。

联网配置,选择创建好的VPC,子网,创建Role允许宿主机上的ECS代理调用ECS服务的API。

创建后画面下面会显示集群信息

集群一览会显示

修改ECS宿主机集群的安全组,inbound源设置为建好的应用负载均衡器的安全组ID

3.1.4 ECR镜像仓库创建

创建一个用于Build阶段上传存放软件工程Docker镜像的镜像仓库

ECS界面下,创建存储库,创建好后如下

3.1.5 S3桶创建

创建一个S3桶用来存放Deploy阶段CloudFormation使用的脚本模版,创建桶时选择和以上服务同一Region,并且打开桶的版本控制。

将CloudFormation模版压缩zip后上传到桶中。

示例中将模版文件service.yaml放在templates文件夹后压缩为templates.zip。

service.yaml如下,注意缩进

Parameters:

  Tag:

    Type: String

    Default: latest


  DesiredCount:

    Type: Number

    Default: 0


  TargetGroup:

    Type: String


  Cluster:

    Type: String


  Repository:

    Type: String


Resources:

  ECSServiceRole:

    Type: AWS::IAM::Role

    Properties:

      Path: /

      AssumeRolePolicyDocument: |

        {

            "Statement": [{

                "Effect": "Allow",

                "Principal": { "Service": [ "ecs.amazonaws.com" ]},

                "Action": [ "sts:AssumeRole" ]

            }]

        }

      Policies:

        - PolicyName: root

          PolicyDocument:

            Version: 2012-10-17

            Statement:

              - Resource: "*"

                Effect: Allow

                Action:

                  - ec2:*

                  - elasticloadbalancing:*


  Service:

    Type: AWS::ECS::Service

    Properties:

      Cluster: !Ref Cluster

      Role: !Ref ECSServiceRole

      DesiredCount: !Ref DesiredCount

      TaskDefinition: !Ref TaskDefinition

      LoadBalancers:

        - ContainerName: simple-app

          ContainerPort: 80

          TargetGroupArn: !Ref TargetGroup


  TaskDefinition:

    Type: AWS::ECS::TaskDefinition

    Properties:

      Family: !Sub ${AWS::StackName}-simple-app

      ContainerDefinitions:

        - Name: simple-app

          Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repository}:${Tag}

          EntryPoint:

            - /usr/sbin/apache2

            - -D

            - FOREGROUND

          Essential: true

          Memory: 128

          MountPoints:

            - SourceVolume: my-vol

              ContainerPath: /var/www/my-vol

          PortMappings:

            - ContainerPort: 80

          Environment:

            - Name: Tag

              Value: !Ref Tag

        - Name: busybox

          Image: busybox

          EntryPoint:

            - sh

            - -c

          Essential: false

          Memory: 128

          VolumesFrom:

            - SourceContainer: simple-app

          Command:

            - /bin/sh -c "while true; do /bin/date > /var/www/my-vol/date; sleep 1; done"

      Volumes:

        - Name: my-vol

到此基础设施部分的搭建工作结束。

3.2   Pipeline的搭建

分为Source,Build以及Deploy三阶段:

Source阶段设置GitHub上的软件工程位置,并设置Deploy阶段会使用的CloudFormation脚本模版来更新ECS服务,

Build阶段使用AWS CodeBuild来打包软件工程到Docker镜像并上传到ECR,

Deploy阶段使用Source阶段引入的CloudFormation脚本,找到对应的宿主机集群,负载均衡器,以及上传到ECR的Docker镜像等对象,更新服务。

AWS CodePipeline创建后的展示图是这样的,串起了整个CICD流程

在AWS CodePipeline界面点击创建管道Pipeline,可以看到画面左侧一个基本流程,从源,到生成Build,到部署Deploy,到角色等配置。实际应用中用户可以随实际需要,或随着CICD流程的由简入繁在创建后编辑加入新的阶段或操作。

点击下一步。

3.2.1 Source阶段配置

源提供商下拉菜单选择GitHub,

点击连接到GitHub来授权访问权,来允许AWS CodePipeline从GitHub上获取软件工程源内容,认证后选择GitHub上软件工程所在位置和分支

此次实例使用的Demo软件工程可以从以下链接Fork:

https://github.com/awslabs/ecs-demo-php-simple-app

点击下一步。

3.2.2 Build阶段配置

AWS CodePipeline在Build阶段支持包括AWS CodeBuild,Jenkins在内的引擎,此方案选用AWS 托管的CodeBuild服务

选择新建构建项目

选择AWS CodeBuild托管的镜像,支持Ubuntu系统,运行时支持包括Java,Python,Go语言,Node.js,Docker在内的众多选择,此次方案使用Docker。

构建规范这里选择使用buildspec.yml,里面预定了AWS CodeBuild在Build生命周期中要执行的动作,如login到ECR,打包Docker镜像,给Docker镜像打tag,上传Docker镜像到已login的ECR镜像仓库。

Buildspec.yml放在GitHub软件工程源代码目录中,如果复制粘贴的话注意yaml文件的缩进

version: 0.2

phases:

  pre_build:

    commands:

      - $(aws ecr get-login)

      - TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)"

  build:

    commands:

-    docker build --tag "替换创建好的ECR镜像仓库的URI:${TAG}" .

  post_build:

    commands:

      - docker push "替换创建好的ECR镜像仓库的URI:${TAG}"

      - printf '{"tag":"%s"}' $TAG > build.json

artifacts:

  files: build.json

选择Role

新建一个Role,这个Role允许AWS CodeBuild来调用相关的AWS服务,此方案中需要调用包括S3,ECR,CloudWatch在内的服务。

*默认创建的Role不具备对ECR的权限,需要在保存构建项目后,到IAM找到新创建的Role,编辑添加对ECR的权限否则后面Pipeline执行到Build时会报错。

保存构建项目。

点击下一步。

3.2.3 Deploy

AWS CodePipeline部署阶段支持包括AWS CloudFormation,AWS CodeDeploy,AWS Elastic Beanstalk在内的服务提供商,此方案选用AWS CloudFormation来部署ECS容器服务。

这里暂时选择无部署,等Pipeline创建好后,编辑引入Deploy的CloudFormation模版源,再进行配置。

点击下一步。

3.2.4 角色

配置AWS CodePipeline对AWS服务的调用权限,包括S3,AWS CodeBuild,AWS CloudFormation,IAM等。点击创建角色到IAM界面选择相对应的策略创建。

创建好后画面回到Pipeline,IAM创建好的Role已经显示在里面。

点击下一步。

3.2.5 审核后创建管道。

管道创建好后会自动运行,现有的从GitHub软件工程源代码抓取工程,打包Docker镜像并推送到ECR上。界面上显示如图

3.2.6 添加Deploy阶段CloudFormation需要的模版源以及配置Deploy阶段

点击编辑,点击Source阶段右上角的画笔图标

可以看到AWS CodePipeline的编辑界面在南北纵向和东西横向都可以添加

在GitHub这个Source右侧,点击添加操作,选择源,操作名称Template,选择S3,输入创建好的S3桶的地址

画面往下拉,注意在输出项目这里,输入Template。

Pipeline中各阶段的传递需要制定南北向的输入输出,即Source阶段S3源的输出Template,在Deploy阶段用输入Template来衔接。

点击更新。

点击Build阶段下面的添加阶段,画面右侧选择部署,选择AWS CloudFormation

操作模式选择创建或更新堆栈,输入创建的堆栈名称,模版这里输入Template::templates/service.yaml,也就是对应的输入是S3源桶中templates.zip里的service.yaml文件。功能选择CAPABILITY_NAMED_IAM。

同样需要创建一个Role,允许AWS CloudFormation调用包括IAM,ECS,ECR在内的AWS服务。

在IAM界面创建好后选择Role。

高级这里点开

在参数覆盖这里输入CloudFormation需要传入的参数,其中的固定参数也可以在S3的service.yaml中直接定义。

{

  "Tag" : { "Fn::GetParam" : [ "MyAppBuild", "build.json", "tag" ] },

  "DesiredCount": "2",

  "Cluster": "CICD-DEMO-CLUSTER-01",

  "TargetGroup": "arn:aws:elasticloadbalancing:us-east-2:305890642658:targetgroup/CICD-DEMO-TG-01/b7649674ee8ab97b",

  "Repository": "cicd-demo-ecr-01"

}

Tag是Build阶段传出的Docker镜像Tag使用的值,传入CloudFormation中用于建立Task Definition的Container时从ECR拉取对应版本的Docker镜像。

DesiredCount,即想要在ECS的Service中建立的Task的数量。

Cluster,即建立好的宿主机集群的名称。

TargetGroup,即建立好的宿主机集群的应用负载均衡器的ARN。

Repository,即建立好的ECR的镜像仓库名称。

 

输入项目这里输入Build阶段和S3模版源的输出。

点击添加操作。

保存管道更改。

4. 运行以及模拟版本更新

4.1   运行

访问负载均衡器的DNS地址来确认目前服务已经运行正常。

4.2   模拟版本更新

4.2.1 修改代码

在开发本地更新代码,示例中在src/index.php加入一行文字

<h1 style="color:FF7F00;">Amazon ECS Awesome!</h1>

4.2.2 提交新版本后查看AWS CodePipeline各阶段触发

4.2.3 刷新服务来确认新修改的部分已经发布

作者简介

郭威,目前在AWS负责推广针对初创企业的最佳云计算架构实践。具有8年软件设计研发,存储,容器与云计算方案经验。曾任某跨国外企高级研发经理与售前经理,在广电视频行业,金融行业等方面有丰富的云计算经验。后加入某容器创业公司任产品研发与容器云方案工作。现致力于容器服务与Devops等方向的学习与研究。