分类目录归档:CodeDeploy

appspec.yml文件解析

appspec.yml是YAML格式、用于定于CodeDeploy服务在整个阶段所做的操作和文件拷贝路径和权限等。首先,这个文档名称必须是appspec.yml,而且文档中的空格个数也有严格的要求,文章最后面会讲到。
appspec.yml文档结构:

version: 0.0
os: operating-system-name
files:
  source-destination-files-mappings
permissions:
  permissions-specifications
hooks:
  deployment-lifecycle-event-mappings

version这里,类似于aws api的版本号,目前只能写0.0
os这里可接受的选项是linux和windows,全小写
files段,定于文件映射关系,例如下面这段

files:
   - source: Config/config.txt
     destination: /webapps/Config
   - source: source
     destination: /webapps/myApp

source文件路径是是相对于本版包的相对路径,如果是/,表示本版包里的全部文件和目录
destination这里是被部署服务器的完整路径(绝对路径)

permission段:用于定义和描述被拷贝到目标服务器上的文件拷贝后的权限
例如下面这个例子

permissions:
   - object: /home/webapp/tomcat/webapps/cms-front
     pattern: "**"
     except: /home/webapp/tomcat/webapps/cms-front/version.ini
     owner: webapp
     group: webapp
     mode: 644
     acls: 
       - u:read-only:r
       - u:wangfei:rw
     context:
       user: unconfined_u
       type: httpd_sys_content_t
       range: s0
     type:
       - file

object是必选项,也就是你的版本包部署路径
pattern是可选项,用于匹配想要赋权的文件,"**"和不填,表示匹配所有文件(可选项)
except就是排除在pattern中被匹配的文件(可选项)
owner和group好理解,改变文件属主(可选项)
mode这里可接受类似644和755这样的权限,或者4755这样的带粘滞位的赋权(可选项)
acls中设置Access Control List,这里我们可以给read-only用户只读权限(可选项)
context是给SELinux用的,不熟悉(可选项)
type可选项是file和directory,用于表述被赋权对象类型,只是文件,还是只是目录,不填表示版本包里的所有文件和目录

hooks区段就是定义各个阶段执行的操作
先介绍下CodeDeploy整个部署过程
1. Start,不可自定义操作
2. ApplicationStop,可定义停服过程
3. DownloadBundle,下载版本包,不可自定义该阶段操作,linux系统下,版本包会被下载到/opt/codedeploy-agent/deployment-root/deployment-group-id/deployment-id/deployment-archive,windows系统会被下载到C:\ProgramData\Amazon\CodeDeploy\deployment-group-id\deployment-id\deployment-archive
4. BeforeInstall,定义版本包文件复制到目标目录之前的操纵,这里可定义备份操作
5. Install,该过程不可被hook,只能通过appspec.yml中的路径和权限等来定义,例如file区段,定义文件拷贝路径
6. AfterInstall,文件复制完之后的操作,可以吧permisson中的定义在此处用脚本来实现
7. ApplicationStart,可被hook来进行起服前的自定义行为
8. ValidateService,部署被验证完成之后可被hook,可用于定义完成之后清理现场。
9. End,结束,不可被hook

hooks:
   deployment-lifecycle-event-name
     - location: script-location
       timeout: timeout-in-seconds
       runas: user-name

hook里面的deployment-lifecycle-event-name,可选项为:
ApplicationStop,BeforeInstall,AfterInstall,ApplicationStart,ValidateService
location写该阶段需要执行的脚本文成和路径,相对版本包的相对路径,例如scripts/full_backup.sh,location可以写多个,每行一个。
timeout时间为超时的秒数,超过指定时间脚本还没完成就算部署失败,默认是1800秒。注意hook阶段的总超时时间是1小时,也就是说在该阶段所有脚本的执行时间必须在1小时内完成,否则会被认为是失败了。
runas,可选项,定义脚本执行的身份

关于文件中的空格必须是指定的个数,例如下面例子中,[4]表示4个空格,不能多也不能少

version:[1]version-number
os:[1]operating-system-name
files:
[2]-[1]source:[1]source-files-location
[4]destination:[1]destination-files-location
permissions:
[2]-[1]object:[1]object-specification
[4]pattern:[1]pattern-specification
[4]except:[1]exception-specification
[4]owner:[1]owner-account-name
[4]group:[1]group-name
[4]mode:[1]mode-specification
[4]acls: 
[6]-[1]acls-specification 
[4]context:
[6]user:[1]user-specification
[6]type:[1]type-specification
[6]range:[1]range-specification
[4]type:
[6]-[1]object-type
hooks:
[2]deployment-lifecycle-event-name:
[4]-[1]location:[1]script-location
[6]timeout:[1]timeout-in-seconds
[6]runas:[1]user-name

解析出来就是这样

version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/html/WordPress
hooks:
  BeforeInstall:
    - location: scripts/install_dependencies.sh
      timeout: 300
      runas: root
  AfterInstall:
    - location: scripts/change_permissions.sh
      timeout: 300
      runas: root
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 300
      runas: root
  ApplicationStop:
    - location: scripts/stop_server.sh
      timeout: 300
      runas: root

用CodeDeploy来做部署、更新和回退

一、准备工作和先决条件

(1)被部署的实例,需要安装codedeploy-agent,安装方法

git clone https://github.com/aws/aws-codedeploy-agent.git
cd aws-codedeploy-agent/bin
./install auto

或者用我从安装过程中截获的rpm包安装也行,分享给大家wget即可
http://cypay-filesharing.s3.amazonaws.com/public/wangfei/codedeploy-agent-1.0-1.643.noarch.rpm
(2)创建一个存放代码包的S3 bucket,此例中叫s3://CYPayCodeDeployBucket,美东地区
(3)创建一个Instance Profile,并使新创建的EC2实例使用该Instance Profile,这个过程中还需要创建个IAM Role,并让Role与Profile关联,该过程授权EC2实例内部的CodeDeploy-Agent能从S3下载版本包。然后再创建一个Service Role给Codedeploy服务用,该服务需要一些EC2权限

大概创建过程:创建个IAM Role,授权AssumeRole和访问版本存放S3,再创建个Instance Profile,然后将之前的Role关联到Profile上;再创建个Role给CodeDeploy服务用;
具体过程:
创建文件 CodeDeployInstanceProfile-Trust.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

再创建一个文件CodeDeployInstanceProfile-Permissions.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "s3:Get*",
        "s3:List*"
      ],
      "Effect": "Allow",
      "Resource":"arn:aws:s3:::CYPayCodeDeployBucket/*"
    }
  ]
}

然后创建角色叫CodeDeployInstanceRole,并给Role加策略

aws iam create-role \
    --role-name CodeDeployInstanceRole \
    --assume-role-policy-document file://CodeDeployInstanceProfile-Trust.json
aws iam put-role-policy \
    --role-name CodeDeployInstanceRole \
    --policy-name CDInstanceRole-Permissions \
    --policy-document file://CodeDeployInstanceProfile-Permissions.json

然后创建instance profile,并把刚才创建的Role附加到instance profile上,这样使用这个instance profile创建的EC2实例,就拥有Role对应的权限和策略

aws iam create-instance-profile \
    --instance-profile-name CodeDeployInstanceProfile
aws iam add-role-to-instance-profile \
    --instance-profile-name CodeDeployInstanceProfile \
    --role-name CodeDeployInstanceRole

至此,一个具有codedeploy相关权限的Instance Profile就创建好了,创建自动部署的EC2实例的时候需要使用该Profile

接下来创建一个Service Role 这个是codedeploy服务用来在EC2实例中部署代码的
创建文件 CodeDeployServiceRole-Trust.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": [
            "codedeploy.us-east-1.amazonaws.com",
            "codedeploy.us-west-2.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

再创建一个文件CodeDeployServiceRole-Permissions.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "autoscaling:PutLifecycleHook",
        "autoscaling:DeleteLifecycleHook",
        "autoscaling:RecordLifecycleActionHeartbeat",
        "autoscaling:CompleteLifecycleAction",
        "autoscaling:DescribeAutoscalingGroups",
        "autoscaling:PutInstanceInStandby",
        "autoscaling:PutInstanceInService",
        "ec2:Describe*"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

创建一个角色CodeDeployServiceRole,然后将角色策略与角色相关联

aws iam create-role \
    --role-name CodeDeployServiceRole \
    --assume-role-policy-document file://CodeDeployServiceRole-Trust.json
aws iam put-role-policy \
    --role-name CodeDeployServiceRole \
    --policy-name CDServiceRole-Permissions \
    --policy-document file://CodeDeployServiceRole-Permissions.json

获取Role的相关信息,复制下来备用

aws iam get-role --role-name CodeDeployServiceRole --query "Role.Arn" --output text
arn:aws:iam::154xxxxx7698:role/CodeDeployServiceRole # mark this down

第(3)步总结:
创建两个Role
CodeDeployInstanceRole
CodeDeployServiceRole
创建一个InstanceProfile
CodeDeployInstanceProfile
注意事项:
Instance Profile必须创建实例的时候选择,事后暂无办法让没选InstanceProfile的实例使用某个Profile
CodeDeploy-agent目前只在最新版的Amazon Linux,Ubuntu,Windows 上测试过,而且自带yum源中没有

二、用CodeDeploy部署个新项目
(1)上一步已经创建好使用CodeDeployInstanceProfile这个Profile的EC2实例,并且打好标签的(例如Name=cms.front),而且实例上已经装好了codedeploy-agent
(2)准备代码,此处用tomcat应用举例。
解压代码,在工程目录目录里添加scripts目录和一个appspec.yml,例如下面这样的目录结构,scripts里面放部署时使用的脚本,appspec.yml文件定义代码部署到哪个,用什么用户在什么阶段执行什么脚本

cms.front/
├── appspec.yml
├── css/
├── error.jsp
├── fonts/
├── html/
├── images/
├── index.jsp
├── js/
├── META-INF/
├── scripts/
├── static/
└── WEB-INF/

appspec.yml文件内容,格式要求非常严格,不允许多余空白

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/tomcat/webapps/
hooks:
  BeforeInstall:
    - location: scripts/check_dependencies.sh
      timeout: 300
      runas: root
  AfterInstall:
    - location: scripts/change_permissions.sh
      timeout: 300
      runas: root
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 300
      runas: ec2-user
  ApplicationStop:
    - location: scripts/stop_server.sh
      timeout: 300
      runas: ec2-user

首次部署,需要安装依赖软件和环境,之后做代码更新时就不需要了
可以把启动脚本写成重启脚本
这四个脚本我就不多说了,自定义性比较强
然后重新打包,只包含内容,不要将上级目录也打进包
cd ~/version/cms.front/ && zip -r cms.front.v1.0.zip *
然后将版本包上传到上述创建的Bucket里CYPayCodeDeployBucket
官方文档中介绍用aws deploy push上传,但是报错了,也没找到解决办法,就用aws s3 cp上传的
Missing required parameter in input: "UploadId"
Unknown parameter in input: "uploadId", must be one of: Bucket, Key, UploadId

更新aws-cli也无用,有解决此错误的请告诉我
然后创建一个Application,理解成一个具有相同功能的一个代码工程,此例为cms.front

aws deploy create-application --application-name CMSTomcatApplication

然后将重新打包好的版本包push到S3上(这步报错了,所以手动传的)

aws deploy push \
  --application-name CMSTomcatApplication \
  --s3-location s3://CYPayCodeDeployBucket/cms.front.v1.0.zip \
  --ignore-hidden-files

然后创建DeploymentGroup,理解成一组需要被部署相同代码的EC2实例,此例为cms.tomcat,按Tag来区分,或则按autoscal组来区分

aws deploy create-deployment-group \
  --application-name CMSTomcatApplication \
  --deployment-group-name CMSTomcatApplication \
  --deployment-config-name CodeDeployDefault.OneAtATime \
  --ec2-tag-filters Key=Name,Value=cms.tomcat,Type=KEY_AND_VALUE \
  --service-role-arn arn:aws:iam::15xxxxxx698:role/CodeDeployServiceRole

最后一行为上面创建的ServiceRole(让吧返回值记录下来来着,现在用到了)
然后创建一个部署,首次部署,跟代码更新部署,和回滚部署,本质是一样,没区别

aws deploy create-deployment \
  --application-name CMSTomcatApplication \
  --deployment-config-name CodeDeployDefault.OneAtATime \
  --deployment-group-name CMSTomcatApplication \
  --s3-location bucket=CYPayCodeDeployBucket,bundleType=zip,key=cms.front.v1.0.zip

返回值

{
    "deploymentId": "d-ROL5D7YPZ"
}

可以查看该部署的状态

aws deploy get-deployment --deployment-id d-ROL5D7YPZ --query 'deploymentInfo.status'
"InProgress" 

等进度从InProgress变成Succeeded就成功了,然后浏览器访问下自己的代码是否OK

三、 做次代码更新和回滚
本质是一样的,只不过版本包不同而已,可以吧首次部署的一些无用脚本删除,回滚的话,会删除就得部署新的
命令行的话,还是这样

aws deploy create-deployment \
  --application-name CMSTomcatApplication \
  --deployment-config-name CodeDeployDefault.OneAtATime \
  --deployment-group-name CMSTomcatApplication \
  --s3-location bucket=CYPayCodeDeployBucket,bundleType=zip,key=cms.front.v1.1.zip

区别在于使用的版本包版本不同
版本更新和回退,使用web console用浏览器来做最爽
选择你的Application,进到DeploymentGroup里,然后选择Deploy New Revision
此时Application和DeploymentGroup的下拉菜单应该已经都默认选好了,还可以改
选择Revision Type,可选为S3或者github,此例为S3 bucket
Revision Location里写上版本包的全路径,s3://CYPayCodeDeployBucket/cms.front.v1.1.zip
File Type会自动识别,支持zip,tar,tar.gz,别的格式没试过,war跟zip一样的吧
Deployment Description写上版本说明
Deployment Config有三种可选
CodeDeployDefault.AllAtOnce, CodeDeployDefault.HalfAtATime, CodeDeployDefault.AllAtATime.
AllAtOnce:组中所有实例一次部署完(同时并发?),有成功的实例就算此次部署成功,都部署失败就算此次部署失败
HalfAtATime:一次部署一半实例,分两次部署完,一半以上实例成功才算部署成功,一半以上失败就算部署失败
AllAtATime:一次部署一台,直至全部完成,所有实例都部署成功才算成功,有失败的就算部署失败(这个最符合我的需求,可以在起服脚本中加上sleep来实现一台一台来更新,并间隔几百秒)
点击Deploy Now来完成部署,等到刷新状态为Succeeded就成功了,接下来就是测试

总结:
创建好Application和DeploymentGroup之后,跟配置管理工程师协定好往版本中添加的文件,然后按时间+项目+版本号的方式打包,并上传到S3上,然后代码的更新回退,由测试和开发同学在浏览器上就能完成,省的自己写代码更新平台了
上新项目的时候,也可以直接利用这个服务来实现,首次部署脚本是得写的复杂点,装上依赖软件和设置环境
用好之后,甚至能不用碰ssh工具就能在浏览器或者用代码把活干完