この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
おはようございます、もきゅりんです。
あけましておめでとうございます。
前回 はMackerelエージェント導入済みAMIをPackerで作成しました。
このAMI、ゴールデンイメージとなれば Auto Scalingにはすぐ反映したい ですよねえ。
Auto Scalingの起動設定の切り替えは地味にめんどくさい作業です。
ということで、CodeCommit -> CodeBuild -> 起動設定の作成 -> Auto Scalingに起動設定の反映 をしてみました。
以下のような図となります。
かなり古い記事ですが、下記を参考にして進めていきます。
AWS CodeBuild と HashiCorp Packer を用いた AMI ビルダーの構築方法
前提条件
- 利用できるAuto Scaling環境があること
- CodeCommitが利用できる状態であること
- IAM権限を設定・更新できること
CodeCommitリポジトリの作成については、『CodeCommit ユーザーガイド』の「セットアップ」を参照してください。
CodeCommitには前回で利用したPackerのTemplateが保存されているとします。
CodeBuildがビルドするのに、mfaの記述は必要ないので削除しておきましょう。
# packer-apache-mackerel.json
{
"builders": [
{
"type": "amazon-ebs",
"ami_name": "apache-mackerel",
"region": "ap-northeast-1",
"source_ami_filter": {
"filters": {
"name": "amzn2-ami-hvm-*-x86_64-gp2"
},
"owners": ["137112412989"],
"most_recent": true
},
"instance_type": "t2.micro",
"ssh_username": "ec2-user"
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"sudo yum -y update",
"sudo yum -y install httpd",
"sudo systemctl start httpd && sudo systemctl enable httpd",
"curl -fsSL https://mackerel.io/file/script/amznlinux/setup-all-yum-v2.sh | MACKEREL_APIKEY='YOUR_MACKEREL_APIKEY' sh",
"sudo sed -i -e '/^#\\s*\\[host_status\\]/s/^#\\s*//' /etc/mackerel-agent/mackerel-agent.conf",
"sudo sed -i -e '/^#\\s*on_start\\s*=\\s*\"working\"/s/^#\\s*//' /etc/mackerel-agent/mackerel-agent.conf",
"sudo sed -i -e '/^#\\s*AUTO_RETIREMENT\\s*=\\s*1/s/^#\\s*//' /etc/sysconfig/mackerel-agent"
]
}
]
}
やること
- コンソールで AWS CodeBuild のプロジェクトを作成する
- ビルドスペックを作成する
- AWS CodeBuild プロジェクトを実行する
- CodePipelineを作成する
1. コンソールで AWS CodeBuild のプロジェクトを作成する
AWS CodeBuild のコンソールからプロジェクトを開始します。
適当な名前を入れます。
今回のソースは CodeCommit
です。
ブランチを選択します。
記事では Ubuntu コンテナを利用していますが、ここでは Amazon Linux2 のコンテナを利用しています。(特に理由はないです。)
新しいサービスロールを作成していますが、既存のロールが環境にあればそれを使っても支障ありません。
後ほど権限を追加するため、利用するロール名は控えておきます。
その他はデフォルト設定で問題で進めます。
控えたIAMロールを検索してPackerに必要となる新しいポリシーを適当な名前で追加します。
(e.g. codebuild-AMI_Builder-ec2-permissions)
Amazon AMI Builder / IAM Task or Instance Role
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action" : [
"ec2:AttachVolume",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CopyImage",
"ec2:CreateImage",
"ec2:CreateKeypair",
"ec2:CreateSecurityGroup",
"ec2:CreateSnapshot",
"ec2:CreateTags",
"ec2:CreateVolume",
"ec2:DeleteKeyPair",
"ec2:DeleteSecurityGroup",
"ec2:DeleteSnapshot",
"ec2:DeleteVolume",
"ec2:DeregisterImage",
"ec2:DescribeImageAttribute",
"ec2:DescribeImages",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:DescribeRegions",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSnapshots",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeVolumes",
"ec2:DetachVolume",
"ec2:GetPasswordData",
"ec2:ModifyImageAttribute",
"ec2:ModifyInstanceAttribute",
"ec2:ModifySnapshotAttribute",
"ec2:RegisterImage",
"ec2:RunInstances",
"ec2:StopInstances",
"ec2:TerminateInstances"
],
"Resource" : "*"
}]
}
ポリシーを作成したら、ロールにアタッチします。
さらにもう1つ、ASGのポリシーも必要です。
今回は若干雑な処置ですが、AutoScalingFullAccess
をアタッチしてしまいます。
2. ビルドスペックを作成する
ビルドで必要な対応を buildspec.yml
に記載します。
やっていることは、PackerでAMIを生成し、起動設定を作成し、指定されたAuto Scaling Group(以下ASG)の起動設定を更新し、現在使用している起動設定を破棄します。
過去の起動設定を破棄したくない場合や各環境変数は環境に応じて編集して下さい。
なお、どこかでコケてもロールバックを行うような処理は考慮されていません。
Packerが生成するAMIのIDを取得するのにこちらを参考としました。
version: 0.2
env:
variables:
ENV: 'dev'
NameTagPrefix: 'ProjectName'
SG: 'SECURITY_GROUP_ID'
InstanceType: 'INSTANCE_TYPE'
VolumeSize: 8
VolumeType: 'gp2'
KeyName: 'SECRET_KEY_NAME'
ASGName: 'AUTO_SCALING_GROUP_NAME'
phases:
install:
runtime-versions:
docker: 18
pre_build:
commands:
- echo "HashiCorp Packer をインストール中..."
- curl -qL -o packer.zip https://releases.hashicorp.com/packer/0.12.3/packer_0.12.3_linux_amd64.zip && unzip packer.zip
- echo "jq をインストール中..."
- curl -qL -o jq https://stedolan.github.io/jq/download/linux64/jq && chmod +x ./jq
- echo "packer-apache-mackerel.json をバリデーションします"
- ./packer validate packer-apache-mackerel.json
build:
commands:
### HashiCorp Packer cannot currently obtain the AWS CodeBuild-assigned role and its credentials
### Manually capture and configure the AWS CLI to provide HashiCorp Packer with AWS credentials
### More info here: https://github.com/mitchellh/packer/issues/4279
- echo "AWS credentials を設定"
- curl -qL -o aws_credentials.json http://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > aws_credentials.json
- aws configure set region $AWS_REGION
- aws configure set aws_access_key_id `./jq -r '.AccessKeyId' aws_credentials.json`
- aws configure set aws_secret_access_key `./jq -r '.SecretAccessKey' aws_credentials.json`
- aws configure set aws_session_token `./jq -r '.Token' aws_credentials.json`
- echo "HashiCorp Packer のテンプレート amazon-linux_packer-template.json をビルド"
- AMIID=`./packer build -machine-readable packer-apache-mackerel.json
| awk -F , '$3 ~ /^artifact$/ && $5 ~ /^id$/ { print $6 };'
| cut -d ":" -f 2`
- echo $AMIID
post_build:
commands:
- echo "HashiCorp Packer によるビルドが完了しました。 `date`"
- echo "AutoScaling Launch Configuration を設定"
- aws autoscaling create-launch-configuration
--launch-configuration-name ${ENV}-${NameTagPrefix}-$(date +%Y%m%d%H%M)
--image-id $AMIID --security-groups $SG --key-name $KeyName
--instance-type $InstanceType
--block-device-mappings "DeviceName=/dev/xvda,Ebs={VolumeSize=8,VolumeType=gp2}"
--instance-monitoring Enabled=false
- echo "AutoScaling Launch Configuration が完了しました"
- echo "AutoScaling の起動設定 を更新します"
- ASG=`aws autoscaling describe-auto-scaling-groups --auto-scaling-group-name ${ASGName}`
- PreLaunchConfigName=`echo $ASG | jq -r .AutoScalingGroups[].LaunchConfigurationName`
- aws autoscaling update-auto-scaling-group
--auto-scaling-group-name $ASGName
--launch-configuration-name ${ENV}-${NameTagPrefix}-$(date +%Y%m%d%H%M)
- echo "AutoScaling の起動設定 を更新が完了しました"
- aws autoscaling delete-launch-configuration
--launch-configuration-name $PreLaunchConfigName
- echo "古いAutoScaling Launch Configuration を削除しました"
3. AWS CodeBuild プロジェクトを実行する
ビルドを開始します。数分の時間がかかります。
成功です。
ASG、起動設定、AMIを確認していきます。
起動設定の名前に利用されている時間はUTCです。
そしてAMI。
特に問題なさそうですね。
ここまでの作業はご覧の通りですが、まぁなかなか面倒な作業ですよね。
ということで、CodePipelineを構築する簡易なCFnテンプレートを作りました。
4. CodePipelineを作成する
以下のテンプレートでは便宜上、SecurityGroupIDやASG名などをパラメータとして指定していますが、実際は別スタックの出力値を取得して利用されるのがよいかと思います。
aws cloudformation deploy --stack-name demo-packer-codepipeline --template-file sample.yml \
--parameter-overrides \
CodeCommitRepositoryName='DemoPacker' \
CodeBuildProjectName='Demo-CodePipeline-AMI-Builder' \
PipelineName='Demo-AMI-Builder-CodePipeline' \
ASGName='ASG_NAME' \
SecurityGroupID='SG_ID' \
InstanceType='t3.micro' \
KeyName='YOUR_KEY_NAME' \
--capabilities CAPABILITY_NAMED_IAM
適宜、適当な名前をパラメータに代入して実行して下さい。
本筋とは関係ありませんが、このなかなかの量のパラメータ数とうまく対応したり、CFnとの付き合い方を語った実益ある弊社ブログを紹介しておきます。
CloudFormationの全てを味わいつくせ!「AWSの全てをコードで管理する方法〜その理想と現実〜」
# sample.yml
AWSTemplateFormatVersion: 2010-09-09
Description: CodePipeline For AMI Update with Packer
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
ENV:
Type: String
Default: 'demo'
NameTagPrefix:
Type: String
Default: 'ProjectName'
CodeCommitRepositoryName:
Type: String
CodeBuildProjectName:
Type: String
PipelineName:
Type: String
ASGName:
Type: String
SecurityGroupID:
Type: 'AWS::EC2::SecurityGroup::Id'
InstanceType:
Type: String
VolumeSize:
Type: Number
Default: 8
KeyName:
Type: 'AWS::EC2::KeyPair::KeyName'
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# S3Bucket
ArtifactBucket:
Type: AWS::S3::Bucket
# CodeWatchEventを実行できるIAMRole
AmazonCloudWatchEventRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: cwe-pipeline-execution
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: codepipeline:StartPipelineExecution
Resource: !Join
- ''
- - 'arn:aws:codepipeline:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref 'PipelineName'
# CodeBuildに適用するIAMRole
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonS3FullAccess
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
- arn:aws:iam::aws:policy/AutoScalingFullAccess
Policies:
- PolicyName: SampleCodeBuildAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Resource: '*'
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- Resource: arn:aws:s3:::codepipeline-${AWS::Region}-*
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
- Resource: !Sub arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${CodeCommitRepositoryName}
Effect: Allow
Action:
- codecommit:GitPull
- Resource: !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/${CodeBuildProjectName}-*
Effect: Allow
Action:
- codebuild:CreateReportGroup
- codebuild:CreateReport
- codebuild:UpdateReport
- codebuild:BatchPutTestCases
- Resource: '*'
Effect: Allow
Action:
- ec2:AttachVolume
- ec2:AuthorizeSecurityGroupIngress
- ec2:CopyImage
- ec2:CreateImage
- ec2:CreateKeypair
- ec2:CreateSecurityGroup
- ec2:CreateSnapshot
- ec2:CreateTags
- ec2:CreateVolume
- ec2:DeleteKeyPair
- ec2:DeleteSecurityGroup
- ec2:DeleteSnapshot
- ec2:DeleteVolume
- ec2:DeregisterImage
- ec2:DescribeImageAttribute
- ec2:DescribeImages
- ec2:DescribeInstances
- ec2:DescribeInstanceStatus
- ec2:DescribeRegions
- ec2:DescribeSecurityGroups
- ec2:DescribeSnapshots
- ec2:DescribeSubnets
- ec2:DescribeTags
- ec2:DescribeVolumes
- ec2:DetachVolume
- ec2:GetPasswordData
- ec2:ModifyImageAttribute
- ec2:ModifyInstanceAttribute
- ec2:ModifySnapshotAttribute
- ec2:RegisterImage
- ec2:RunInstances
- ec2:StopInstances
- ec2:TerminateInstances
# CodePipelineに適用するIAMRole
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: SamplePipeline
PolicyDocument:
Version: 2012-10-17
Statement:
- Resource:
- !Sub arn:aws:s3:::${ArtifactBucket}/*
Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketVersioning
- Resource: '*'
Effect: Allow
Action:
- codecommit:GetRepository
- codecommit:ListBranches
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
- codecommit:CancelUploadArchive
- codebuild:StartBuild
- codebuild:StopBuild
- codebuild:BatchGet*
- codebuild:Get*
- codebuild:List*
- codecommit:GetBranch
- codecommit:GetCommit
- iam:PassRole
# CloudWatchEventの実行ルール
AmazonCloudWatchEventRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- aws.codecommit
detail-type:
- 'CodeCommit Repository State Change'
resources:
- !Join
- ''
- - 'arn:aws:codecommit:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref 'CodeCommitRepositoryName'
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- branch
referenceName:
- master
Targets:
- Arn: !Join
- ''
- - 'arn:aws:codepipeline:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref 'PipelineName'
RoleArn: !GetAtt AmazonCloudWatchEventRole.Arn
Id: codepipeline-AppPipeline
# CodeBuild
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: CODEPIPELINE
Environment:
PrivilegedMode: true
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-x86_64-standard:2.0
Type: LINUX_CONTAINER
EnvironmentVariables:
- Name: AWS_DEFAULT_REGION
Value: !Ref AWS::Region
- Name: ENV
Value: !Ref ENV
- Name: NameTagPrefix
Value: !Ref NameTagPrefix
- Name: SecurityGroupID
Value: !Ref SecurityGroupID
- Name: InstanceType
Value: !Ref InstanceType
- Name: VolumeSize
Value: !Ref VolumeSize
- Name: VolumeType
Value: 'gp2'
- Name: KeyName
Value: !Ref KeyName
- Name: ASGName
Value: !Ref ASGName
Name: !Ref CodeBuildProjectName
ServiceRole: !Ref CodeBuildServiceRole
Source:
Type: CODEPIPELINE
BuildSpec: |
version: 0.2
phases:
install:
runtime-versions:
docker: 18
pre_build:
commands:
- echo "HashiCorp Packer をインストール中..."
- curl -qL -o packer.zip https://releases.hashicorp.com/packer/0.12.3/packer_0.12.3_linux_amd64.zip && unzip packer.zip
- echo "jq をインストール中..."
- curl -qL -o jq https://stedolan.github.io/jq/download/linux64/jq && chmod +x ./jq
- echo "packer-apache-mackerel.json をバリデーションします"
- ./packer validate packer-apache-mackerel.json
build:
commands:
### HashiCorp Packer cannot currently obtain the AWS CodeBuild-assigned role and its credentials
### Manually capture and configure the AWS CLI to provide HashiCorp Packer with AWS credentials
### More info here: https://github.com/mitchellh/packer/issues/4279
- echo "AWS credentials を設定"
- curl -qL -o aws_credentials.json http://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > aws_credentials.json
- aws configure set region $AWS_REGION
- aws configure set aws_access_key_id `./jq -r '.AccessKeyId' aws_credentials.json`
- aws configure set aws_secret_access_key `./jq -r '.SecretAccessKey' aws_credentials.json`
- aws configure set aws_session_token `./jq -r '.Token' aws_credentials.json`
- echo "HashiCorp Packer のテンプレート amazon-linux_packer-template.json をビルド"
- echo "HashiCorp Packer のテンプレート amazon-linux_packer-template.json をビルド"
- AMIID=`./packer build -machine-readable packer-apache-mackerel.json
| awk -F , '$3 ~ /^artifact$/ && $5 ~ /^id$/ { print $6 };'
| cut -d ":" -f 2`
- echo $AMIID
post_build:
commands:
- echo "HashiCorp Packer によるビルドが完了しました。 `date`"
- echo "AutoScaling Launch Configuration を設定"
- aws autoscaling create-launch-configuration
--launch-configuration-name ${ENV}-${NameTagPrefix}-$(date +%Y%m%d%H%M)
--image-id $AMIID --security-groups $SG --key-name $KeyName
--instance-type $InstanceType
--block-device-mappings "DeviceName=/dev/xvda,Ebs={VolumeSize=8,VolumeType=gp2}"
--instance-monitoring Enabled=false
- echo "AutoScaling Launch Configuration が完了しました"
- echo "AutoScaling の起動設定 を更新します"
- ASG=`aws autoscaling describe-auto-scaling-groups --auto-scaling-group-name ${ASGName}`
- PreLaunchConfigName=`echo $ASG | jq -r .AutoScalingGroups[].LaunchConfigurationName`
- aws autoscaling update-auto-scaling-group
--auto-scaling-group-name $ASGName
--launch-configuration-name ${ENV}-${NameTagPrefix}-$(date +%Y%m%d%H%M)
- echo "AutoScaling の起動設定 を更新が完了しました"
- aws autoscaling delete-launch-configuration
--launch-configuration-name $PreLaunchConfigName
- echo "古いAutoScaling Launch Configuration を削除しました"
# CodePipeLine
CodePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !GetAtt CodePipelineServiceRole.Arn
Name: !Ref PipelineName
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
Stages:
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: AWS
Version: 1
Provider: CodeCommit
Configuration:
RepositoryName: !Ref CodeCommitRepositoryName
PollForSourceChanges: false
BranchName: master
RunOrder: 1
OutputArtifacts:
- Name: App
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
Configuration:
ProjectName: !Ref CodeBuildProject
RunOrder: 1
InputArtifacts:
- Name: App
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
PipelinelogicalID:
Description: logical ID.
Value: !Ref CodePipeline
成功です。
5. 最後に
実用する際はもう少々考慮、改善点がありそうですが、ソースリポジトリをCodeCommitをGitHubにしてもやることに大差ありません。
手前味噌で恐縮ですが、下記などお役に立つでしょう。
CFnでGitHub + Fargate + CodePipelineを構築してみる
以上です。
どなたかのお役に立てば幸いです。