前言
1.1介绍
亚马逊云科技推出的开发者服务,包括了Amazon CodePipeline, Amazon CodeCommit, Amazon CodeBuild, Amazon CodeDeploy, Amazon CodeStar等DevOps工具集,实现了建立自动化CI/CD流水线。但是一般企业用户或多或少都已经有部分自己正在使用的CI/CD工具,比较常用的如Jenkins。企业用户需要的是一条可自由定制的流水线,补全自己CI/CD流水线的不足。
本文基于亚马逊云科技开发者服务包括Amazon CodeCommit、Amazon CodeDeploy,再结合中国企业客户常用的主流开源CI/CD工具比如Jenkins、 Sonar、Maven等,演示在亚马逊云上构建自动化CI/CD流水线。
1.2 环境说明
-
支持Java语言、开发框架选择Spring Boot,应用程序示例为Java后端查询的API接口,可以从Github上下载。
-
代码使用亚马逊CodeCommit托管,代码静态检查使用Sonar,流水线管道采用了Jenkins,代码构建使用Maven,单元测试框架是Junit,部署工具使用Amazon CodeDeploy。
-
支持将通过测试的Java应用程序部署到亚马逊云上Autoscaling组的Amazon EC2服务器中。
解决方案概述
此解决方案可在由西云数据运营的亚马逊云科技(宁夏)区域或由光环新网运营的亚马逊云科技(北京)区域中部署,也可以在海外区域部署。
1
系统架构
该方案的系统构架图如下:
当流水线创建成功后,用户就可以利用本地的代码编辑IDE比如Eclipse或者亚马逊云上提供的Cloud9完成Java代码的拉取,编辑和提交代码,从而触发流水线执行。CI/CD流水线中从Jenkins从Amazon CodeCommit拉取代码、编译代码、静态扫描、单元测试、集成测试、打包、把产物保存在Amazon S3、调用Amazon CodeDeploy执行部署到Auto Scaling组的Amazon EC2上。
2
部署步骤
-
终端环境准备
-
创建存储桶
-
创建Amazon CodeCommit存储库
-
创建Jenkins
-
配置Amazon CodeDeploy服务
-
创建系统运行环境
1. 终端环境准备
请确保本地终端或能访问亚马逊云科技服务的终端已安装亚马逊云科技cli并配置的账号的访问凭证,使用 Linux 和有 root 权限,并且使用x86架构时,可以使用以下命令进行安装:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
*左滑查看更多
使用命令行Amazon configure配置访问凭证。
2.创建存储桶
在终端中运行以下命令,创建S3存储桶用于存放编译后的包,其中<123456789000>替换为自己的亚马逊云科技账户id(控制台右上角可以找到):
aws s3 mb s3://devops-template-build-packages-<123456789000>
3.创建CodeCommit存储库
打开亚马逊云科技管理控制台,进入Amazon CodeCommit服务
-
选择右边菜单栏存储库,点击创建存储库, 输入存储库名字devops-template-project,及其他可选配置,点击创建
-
默认用当前用户访问存储库,用户需要具有Amazon CodeCommit访问权限,也可以创建访问该存储库的新用户
-
点击当前用户,进入用户配置详细页面,点击安全证书子页面
-
安全证书设置页面中生成Amazon CodeCommit的HTTPS Git凭证,保存下载用户和密码
-
在本地终端命令行执行以下命令,将pipeline及脚手架工程下载到本地:
git clone https://github.com/JiangKeJacky/aws-cicd-pipeline.git
*左滑查看更多
Aws-cicd-pipeline文件包含两部分:pipeline包含构建pipeline环境执行脚本和代码;spring-test是一个包含单元测试、部署规范、部署脚本的spring工程脚手架,用户可根据需要在脚手架的基础上构建工程。
IDE中配置存储库地址及访问用户名和密码,把示例代码提交到存储库。也可以使用命令行如下,其中和为上一步创建的用户名和密码:
cp -r aws-cicd-pipeline/spring-tests ./
cd spring-tests
git init
git add .
git commit -m "first commit"
git remote add origin https://<username>:<password>@git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/devops-template-project
git push -u origin master
*左滑查看更多
-
在亚马逊控制台,进入Amazon CodeCommit,存储库devops-template-project中,查看脚手架代码是否上传成功
4.创建Jenkins
为简化安装配置,本文将Jenkins/Sonar/maven环境配置完,打包成一个映像AMI,可通过AMI启动Amazon EC2快速完成配置,也可以自己参考Jenkins、Sonar、Maven官网说明在Amazon EC2上安装配置。
-
进入Amazon EC2服务页面,点击右侧菜单映像AMI,选择公有映像,筛选AMI ID: ami-09ee66d2d7dd20ad1(中国区),ami-0c60e8d2f933706e3(海外区),获取 Jenkins-Sonar 的 AMI 镜像
-
从 Jenkins-Sonar 的 AMI 启动Amazon EC2 新实例,点击操作->启动
-
选择 t2.large 的类型,Amazon EC2类型可根据工作负载选择调整
-
选择自动分配公网 IP,IAM角色选择具有Amazon S3存储桶读写权限、Amazon CodeCommit、Amazon CodeDeploy访问权限的角色,其他用默认选项
-
安全组入口开放 22,8080 和 9000 端口,22 端口用于远程 SSL 连接,8080 端口用于 Jenkins 访问,9000 用于 Sonar 访问。来源为允许访问的地址。注:由于中国区互联网域名访问需要ICP备案,因此在中国区不能正常访问使用可限制内网地址访问或者通过IP地址访问。
-
实例启动后,可在终端通过 ssh 连接到实例,ssh -i ee-default-keypiart.pem ec2-user@jenkinsserverip
-
命令行 sudo lsof -i:8080和sudo lsof -i:9000检查 jenkins 和 sonar 是否启动
-
如果 jenkins 未成功启动,命令行 sudo systemctl start jenkins启动 jenkins
-
如果 sonar 未成功启动,命令行sudo docker start sonarqube999启动 sonar
-
Jenkins 默认用户名密码为:root/abcd1234,登录后修改密码
-
Sonar 默认用户名密码为:admin/abcd1234,登录后修改密码
-
在本地浏览器输入Amazon EC2 服务器的域名及 sonar 端口号 9000,如http://ec2-13-213-61-124.cn-northwest-1.compute.amazonaws.com:9000
-
打开本地浏览器输入该Amazon EC2 服务器的域名及 Jenkins 端口号 8080,如http://ec2-13-213-61-124.c-northwest-1.compute.amazonaws.com:8080,将进入Jenkins配置页面
-
配置Amazon CodeCommit访问凭证,通过 Manage Jenkins->Manage Credentials
选择codecommit-user:
点击右边菜单的更新,输入devuser的用户名和密码,并保存。
-
返回Jenkins DashBoard 中有 3 个预设的 pipeline,本文使用cicd-pipeline-codecommit-codedeploy,点击进入修改cicd-pipeline-codecommit-codedeploy 配置
-
点击流水线,修改AWS_S3_BUCKET_CODE_PACKAGE环境变量为准备阶段创建的S3存储桶devops-template-build-packages-<123456789000>保存该 pipeline
-
cicd-pipeline-codecommit-codedeploy的pipeline脚本定义如下:
import groovy.json.JsonSlurper pipeline { agent any options { timeout(time: 30, unit: 'MINUTES') } environment { PROJECT_NAME = "spring-test-unit" JENKIN_ROLE_NAME = 'role-Deploy' ROOT_BACKEND_PATH = "${WORKSPACE}" RUN_ENV = "prd" VERSION_NUMBER = "0.0.${BUILD_NUMBER}.${RUN_ENV}" MVN_PACKAGE_NAME = "target/${PROJECT_NAME}-0.0.1-SNAPSHOT.zip" APPLICATION_DIR = "${PROJECT_NAME}" //GIT repository GIT_REPOSITORY = "https://git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/devops-template-project.git" AWS_DEFAULT_REGION="ap-southeast-1" //s3 bucket for store the code package, should be created before the pipeline first run AWS_S3_BUCKET_CODE_PACKAGE = "devops-template-build-packages-123456789000" //initialize codedeploy for a pipeline should be done before the pipeline first run AWS_DEPLOYMENT_APPLICATION_NAME = "SpringBoot_Test" AWS_DEPLOYMENT_GROUP = "SpringBoot_DepGroup" PATH = "/usr/local/lib/apache-maven-3.8.1/bin:$PATH" //setting for sonar SONAR_SERVER = "http://localhost:9000" SONAR_TOKEN = "d4c1d7d45ac038ccfcee734f1c20c9ef43a5d535" DEPLOY_ID = "" } stages { stage('PullSource') { steps { //credential should be created in jenkins global credential, with user and password generated by codecommit git credentialsId: 'codecommit-user', url: "${GIT_REPOSITORY}" } } stage('SourceScan'){ steps{ sh "mvn clean verify sonar:sonar -Dsonar.host.url=${SONAR_SERVER} -Dsonar.login=${SONAR_TOKEN} -Pcoverage" } } stage('Compile') { steps { //代码编译 sh "mvn clean compile" } } stage('UnitTest') { steps { //单元测试 sh "mvn -Dtest=com.mgiglione.service.test.unit.UnitTests test" } } stage('IntegrationTest') { steps { //集成测试 sh "mvn -Dtest=com.mgiglione.service.test.integration.IntegrationTests test" } } stage('Package') { steps { //代码打包 sh "mvn -Dmaven.test.skip=true package" } } stage('PackageToS3' ) { steps{ script{ def UPLOAD_SCRIPT = ''' aws s3 ls aws s3 cp ${MVN_PACKAGE_NAME} s3://${AWS_S3_BUCKET_CODE_PACKAGE}/${APPLICATION_DIR}_${VERSION_NUMBER}.zip ''' sh UPLOAD_SCRIPT echo "VERSION_NUMBER: ${VERSION_NUMBER}" } } } stage('Deploy') { steps{ script{ def DEPLOY_SCRIPT = ''' aws --region ${AWS_DEFAULT_REGION} deploy create-deployment \ --application-name ${AWS_DEPLOYMENT_APPLICATION_NAME} \ --deployment-group-name ${AWS_DEPLOYMENT_GROUP} \ --deployment-config-name CodeDeployDefault.OneAtATime \ --s3-location bucket=${AWS_S3_BUCKET_CODE_PACKAGE},key=${APPLICATION_DIR}_${VERSION_NUMBER}.zip,bundleType=zip ''' result = sh returnStdout: true, script: DEPLOY_SCRIPT result = result.trim() echo result def deployment = new JsonSlurper().parseText(result) if (deployment == null) error 'Deploy fail' DEPLOY_ID = deployment.deploymentId //result = sh returnStdout: true, script: DEPLOY_STATUS echo DEPLOY_ID if (DEPLOY_ID == null || DEPLOY_ID == "") error 'Deploy fail' } timeout(2) { waitUntil { script { def DEPLOY_STATUS = '''aws deploy get-deployment --deployment-id '''.concat("${DEPLOY_ID}").concat(''' --query "deploymentInfo.status" --output text ''') //sh DEPLOY_STATUS result = sh returnStdout: true, script: DEPLOY_STATUS echo "Deploy status: " + result if (result.trim().contains("Failed")) { error 'Deployment failed' return true } if (result.trim().contains("Succeeded")) { echo "Deployment succeeded" return true } return false } } } } } stage('AutomaticTest') { steps { sleep 5 script{ def TEST_SCRIPT = ''' cd /home/jenkins python3 -m robot --outputdir results robot/ACT.robot ''' sh TEST_SCRIPT } } } } post { success { emailext(to: 'kejian@amazon.com', subject: '[CICD] ${PROJECT_NAME} - Build # ${BUILD_NUMBER} - ${BUILD_STATUS}!', body: '${PROJECT_NAME} - Build # ${BUILD_NUMBER} - ${BUILD_STATUS}!. Check console output at ${BUILD_URL} to view the results.', compressLog: true, attachLog: true, attachmentsPattern: 'report.html') //mail body: 'Jenkins Pipeline Build #${env.BUILD_NUMBER} Finished. Check console output at $BUILD_URL to view the results.', from: 'kejian@amazon.com', replyTo: 'kejian@amazon.com', subject: 'mail ${env.PROJECT_NAME - Build # ${env.BUILD_NUMBER} - ${env.BUILD_STATUS}!', to: 'kejian@amazon.com' } failure { emailext(to: 'kejian@amazon.com', subject: '[CICD] ${PROJECT_NAME} - Build # ${BUILD_NUMBER} - ${BUILD_STATUS}!', body: '${PROJECT_NAME} - Build # ${BUILD_NUMBER} - ${BUILD_STATUS}!. Check console output at ${BUILD_URL} to view the results.', compressLog: true, attachLog: true, attachmentsPattern: 'report.html') } } }
*左滑查看更多
其中,与Amazon Codecommit服务集成代码如下:
git credentialsId: 'codecommit-user', url: "${GIT_REPOSITORY}"
与Amazon CodeDeploy服务集成调用aws deploy create-deployment
命令,由于Amazon CodeDeploy服务是异步调用,采用waitUntil
语句查询部署服务的状态,具体调用了aws deploy get-deployment
。
在AutomaticTest阶段集成的自动测试框架RobotFramework可以根据需要选择使用。在Pipeline执行完成时,Post阶段可以发送邮件,此功能需要配置Jenkins的smtp服务。
5.配置Amazon CodeDeploy服务
在本地终端中创建IAM角色和策略。如果在中国区使用请将create-codedeploy-role.sh和create-codedeploy-project.sh中资源路径arn:aws修改为arn:aws-cn,例如:create-codedeploy-project.sh中 arn:aws:iam:: <123456789000>:role/CodeDeployServiceRole 在中国区使用修改arn为: arn:aws-cn:iam:: <123456789000>:role/CodeDeployServiceRole, <123456789000> 修改为自己的亚马逊云科技账号
cd ../aws-cicd-pipeline/pipeline
vim create-codedeploy-project.sh
执行脚本:
./create-codedeploy-role.sh
./create-codedeploy-project.sh
6.创建系统运行环境
执行以下命令,将自动创建3台Amazon EC2服务器:
aws ec2 create-security-group --group-name CodeDeployDemo-SG --description "CodeDeployDemo test security group"
*左滑查看更多
输出如下:
{
“GroupId”: “sg-0177142e880071b22”
}
GroupId值”sg-0177142e880071b22”在后面命令中使用
aws ec2 authorize-security-group-ingress \
--group-name CodeDeployDemo-SG \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-name CodeDeployDemo-SG \
--protocol tcp \
--port 8080 \
--cidr 0.0.0.0/0
aws autoscaling create-launch-configuration \
--launch-configuration-name CodeDeployDemo-AS-Configuration \
--image-id ami-0e8e39877665a7c92 \
--key-name ee-default-keypair \
--security-groups <sg-0177142e880071b22> \
--iam-instance-profile CodeDeployDemo-EC2-Instance-Profile \
--instance-type t3.small
<sg-0177142e880071b22>为在第一条命令行中输出的“GroupId”值
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name CodeDeployDemo-AS-Group \
--launch-configuration-name CodeDeployDemo-AS-Configuration \
--min-size 3 \
--max-size 3 \
--desired-capacity 3 \
--vpc-zone-identifier "<subnet-f4fb7c92>,<subnet-3003b778>" \
--tags Key=Name,Value=CodeDeployDemo,PropagateAtLaunch=true
aws ssm create-association \
--name AWS-ConfigureAWSPackage \
--targets Key=tag:Name,Values=CodeDeployDemo \
--parameters action=Install,name=AWSCodeDeployAgent \
--schedule-expression "cron(0 2 ? * SUN *)"
*左滑查看更多
vpc-zone-identifier在VPC服务的子网页面可以找到:
创建应用负载均衡器ALB监听80端口:
3
运行使用Pipeline
-
在jenkins的cicd-pipeline-codecommit-codedeploy中,点击build now开始新的构建
在浏览器中输入:http://:8080/manga/sync/ken 验证部署的应用是否返回关键字为ken的查询结果。
-
修改
spring-tests/src/main/java/com/example/controller/MangaController.java的
代码
*左滑查看更多
或者
cd ../../spring-tests
vim src/main/java/com/example/controller/MangaController.java
*左滑查看更多
加入以下接口,并保存:
@RequestMapping(value = "/helloworld", method = RequestMethod.GET)
public @ResponseBody String sayHello()
{
return "Hello world!";
}
*左滑查看更多
提交到 Amazon CodeCommit, 将触发 Jenkins 的 CICD pipeline,在终端中执行以下命令
git commit -a -m "add new interface helloworld"
git push origin master
*左滑查看更多
在Jenkins中查看pipeline构建进度,pipeline完成后在浏览器中输入http://:8080/manga/helloworld 验证部署的应用。
-
点击某个 build,在 pipeline 的 Console Out 中查看下详细的输出,包括代码扫描、单元测试、集成测试、编译、打包等过程。
-
由于 Amazon CodeDeploy 是调用异步执行,Amazon CodeDeploy 的详细执行情况在 Amazon CodeDeploy 控制台中查看。
总结
本文介绍了使用Jenkins,集成Amazon CodeCommit、Amazon S3 和 Amazon CodeDeploy服务,构建自动化CI/CD流水线,支持Java应用发布到Amazon EC2服务器上。基于这个流水线有很多扩展空间,比如:
-
集成Amazon CodeBuild服务
-
蓝绿部署
-
权限控制
本文提供AMI中预置的pipeline也包括了打包容器部署Amazon EKS及前端构建的示例。希望基于本文,读者可以快速集成亚马逊云科技开发者服务建立起自己的CICD流水线。
本篇作者
姜可
解决方案构架师
亚马逊云科技资深解决方案架构师,负责协助客户业务系统上云的解决方案架构设计和咨询,现致力于DevOps、IoT、机器学习相关领域的研究。在加入亚马逊云科技之前,曾在金融、制造、政府等行业耕耘多年,对相关行业解决方案和架构有很深的理解。
听说,点完下面4个按钮
就不会碰到bug了!