AWSのEC2を使ってCodeCommitのソースを定期的にCICDしてみた①〜EC2構築編〜
過去記事でHUGOをAWS環境で自動デプロイするための手順を紹介しましたがEC2でデプロイする方法に切り替えたのでその内容を紹介します。
本WebサイトはHUGOで作成しておりビルドしてできた静的ファイルをS3に配置して公開する仕組みになっています。 この構成を見直したため紹介します。
過去記事で紹介していますがCICD(ビルドとデプロイ)はAWSのCodeCommit, CodePipeline, CodeBuildを使っています。
HUGOには予約投稿機能がないため未来の日付で記事を作成しておき定期的にCloudWatchEventでCodePipelineをキックしていました。 しかしこの方法だとCodeBuildの利用料金がそこそこかかってしまうため(とはいっても10USD前後ですが)、なるべく利用料金を下げる方法を模索してEC2でCICDする方法に落ち着きました。
今回は構成変更した際の手順など紹介したいと思います。
やりたかったこと
当初の目的およびその解決策は以下のとおりです。
- 目的:HUGOの記事をあらかじめ作成しておき予約投稿したかった
- 解決策:公開日時に合わせてビルドを行い配信先のサーバやS3に配置(デプロイ)する
静的サイトジェネレーターのHUGOはビルド時点で記事を作成するため予約投稿ができません。 そのためあらかじめ記事を作成しておいて決まった時間に記事を公開するためには目的の時間にビルドを行いファイルを作成する必要があります。
本サイトはAWS環境に構築しているためAWSのリソースを使って公開日時に合わせたビルドとデプロイを実現しています。 その際に使用しているリソースは以下のとおりです。
- 変更前:CloudWatchEvent + CodeCommit + CodePipeline + CodeBuild
- 変更後:CloudWatchEvent + CodeCommit + EC2(+VPC)
実現方法はいくつかあると思いますが本サイトでやっていたのはこの2つです。 CloudWatchEventで公開日時に合わせてCICDをキックするところは同じですがCICDを行うリソースが違います。
どちらも結果は同じですが利用料金が若干ことなるためEC2でのCICDに変更しました。
EC2とCodeBuildの利用料金比較
CodeBuildの利用料金は公式サイトに記載があります。
記載のとおりCodeBuildはインスタンスタイプに応じたビルド1分あたりの料金で計算されます。
たとえば以下の場合はざっくり月額7USDかかります。 ビルド回数を減らせば当然値段もさがりますがその辺を気にしたくないなと思いEC2に切り替えています。
- インスタンスタイプ:general1.small
- Linuxのビルド1分あたりの料金:0.005USD
- 1日のビルド回数:10回
- ビルドにかかる時間:5分
- 30日分のトータルビルド時間:10×5×30=1500分
- 無料利用枠:100分(general1.smallの場合)
- CodeBuild利用コスト:(1500-100)×0.005=7USD
EC2の場合はCICD時のみ起動してそれ以外は停止していたとするとざっくり月額4.2USDです。 CICDだけなら起動時間はもっと短くできますが料金比較のための仮置きしています。
- インスタンスタイプ:t2.medium
- Linuxインスタンス1時間あたりの料金:0.06USD
- 1日のインスタンス起動回数:10回
- インスタンス起動時間:10分
- 30日分のEC2トータル起動時間:10×10×30=3000分(50時間)
- 汎用SSD(GP2):10GB
- EBS1GBあたりの料金:0.12USD
- EC2利用コスト:50×0.06=3USD
- EBS利用コスト:10×0.12=1.2USD
比較するとおおよそ半額近くまで減らせるのではないかと思います。
EC2の利点としては金額の他にビルド時間を短縮できることです。 事前にEC2にビルド環境を準備しておけば毎回必要なモジュールをインストールする必要がないからです。
またCodeBuildの数が多い場合EC2に切り替えて処理すれば大幅に費用を削減できるかもしれません。
EC2のCICD処理の流れ
EC2でビルド・デプロイをする流れは以下のとおりです。
CloudWatchEventでインスタンス起動
インスタンス起動でスクリプト実行
- CodeCommitからビルドするソースを取得
- 取得したソースのビルド
- S3にビルドしたファイルを転送
- CloudFrontのキャッシュをクリア
- ビルドしたファイルの削除
CloudWatchEventでインスタンス停止
起動時に実行する処理の流れはCodeBuildと同じになります。
EC2のCICD環境構築手順
上記の処理を実現するための手順を紹介します。
概要は以下のとおりです。
- EC2インスタンス構築
- CICD設定
HUGOのビルドはDockerを使用しています。 Dockerを使用すればHUGO以外のビルドを行いたい場合も同じEC2インスタンス上で簡単にビルド環境を構築できるためオススメです。
まずはCICDのためのEC2インスタンスを用意しましょう。
CloudFormationを使ったEC2インスタンス構築
今回はCloudFromationを使ってEC2インスタンスを作ってみました。コードは以下になります。
- cmn-network.yaml: VPCなどネットワーク周りの設定
AWSTemplateFormatVersion: "2010-09-09"
Description: Create Common Network
Parameters:
EnvironmentName:
Type: String
Default: cmn
VPCCIDR:
Type: String
Default: 10.0.0.0/16
PublicSubnetCIDR:
Type: String
Default: 10.0.1.0/24
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-vpc
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-igw
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref IGW
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: 'AWS::Region'
CidrBlock: !Ref PublicSubnetCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-Public-subnet
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-Public-rtb
PublicSubnetToInternetRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref IGW
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
EndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: EndpointSecurityGroup
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-endpoint-sg
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
EndpointS3:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref PublicRouteTable
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcEndpointType: Gateway
VpcId: !Ref VPC
Outputs:
VPC:
Value: !Ref VPC
Export:
Name: !Sub ${EnvironmentName}-vpc
VPCCIDR:
Value: !Ref VPCCIDR
Export:
Name: !Sub ${EnvironmentName}-vpccidr
IGW:
Value: !Ref IGW
Export:
Name: !Sub ${EnvironmentName}-igw
PublicSubnet:
Value: !Ref PublicSubnet
Export:
Name: !Sub ${EnvironmentName}-Public-subnet
PublicRouteTable:
Value: !Ref PublicRouteTable
Export:
Name: !Sub ${EnvironmentName}-Public-rtb
SecurityGroup:
Value: !Ref EndpointSecurityGroup
Export:
Name: !Sub ${EnvironmentName}-endpoint-sg
EndpointS3:
Value: !Ref EndpointS3
Export:
Name: !Sub ${EnvironmentName}-endpoint-S3
- cmn-ec2.yaml: EC2インスタンス設定
AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 for Build
Parameters:
EnvironmentName:
Type: String
Default: cmn
Ec2ImageId:
Type: String
Default: ami-0ce107ae7af2e92b5
Ec2InstanceType:
Type: String
Default: t2.medium
KeyPair:
Type: String
Default: xxxxx
Resources:
EC2IAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${EnvironmentName}-ssm-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- Ref: EC2IAMRole
InstanceProfileName: !Sub ${EnvironmentName}-EC2InstanceProfile
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: EC2SecurityGroup
VpcId: !ImportValue cmn-vpc
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-ec2-sg
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref Ec2InstanceType
ImageId: !Ref Ec2ImageId
IamInstanceProfile: !Ref EC2InstanceProfile
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: !ImportValue cmn-Public-subnet
GroupSet:
- !Ref EC2SecurityGroup
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeSize: 10
VolumeType: gp2
SourceDestCheck: true
KeyName: !Ref KeyPair
UserData: !Base64 |
#!/bin/bash
yum update -y
yum install git -y
sudo ln -sf /usr/share/zoneinfo/Japan /etc/localtime
sudo sed -i "s/\"UTC\"/\"Japan\"/g" /etc/sysconfig/clock
sudo sed -i "s/en_US\.UTF-8/ja_JP\.UTF-8/g" /etc/sysconfig/i18n
yum install -y docker
systemctl enable docker
service docker start
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-build-ec2
- Key: AutoShutDown
Value: true
- Key: AutoStartUp
Value: true
Outputs:
EC2SecurityGroup:
Value: !Ref EC2SecurityGroup
Export:
Name: !Sub ${EnvironmentName}-ec2-sg
EC2Instance:
Value: !Ref EC2Instance
Export:
Name: !Sub ${EnvironmentName}-build-ec2
長いのでポイントだけ解説します。
- ファイルは
cmn-network.yaml
とcmn-ec2.yaml
にわけているが1つにまとめてしまってもOK。 - キーペアはあらかじめ作成しておきパラメータ
KeyPair
で指定する。 - EC2へはSSM(AWS Session Manager)を使って接続できるようにするため
EC2IAMRole
,EC2InstanceProfile
を作成。 UserData
を指定してEC2構築時に必要なソフトウェアなどインストール。
今回はDockerを使用するためUserDataにあらかじめインストールコマンドを記述し有効化してあります。 その他gitのインストールおよびAmazon Linuxで行う初期設定をとりあえず入れています。
#!/bin/bash
yum update -y
yum install git -y
sudo ln -sf /usr/share/zoneinfo/Japan /etc/localtime
sudo sed -i "s/\"UTC\"/\"Japan\"/g" /etc/sysconfig/clock
sudo sed -i "s/en_US\.UTF-8/ja_JP\.UTF-8/g" /etc/sysconfig/i18n
yum install -y docker
systemctl enable docker
service docker start
UserDataについては以下に詳しく載っていますので興味ある方は呼んでみてください。
参考情報:cloud-initを使ったLinux OSの初期設定
またSSMについてはこちらにまとまっています。
参考情報:さらば踏み台サーバ。Session Managerを使ってEC2に直接SSHする
【補足】AWSコンソールを使ったCoudFormation実行方法
補足としてCoudFormation実行方法を紹介しておきます。
①AWSコンソールにログインしてCloudFormation管理画面を開く。
②「スタックの作成>新しいリソースを使用(標準)」をクリック。
③テンプレートの指定画面が開く。テンプレートのyamlファイルをS3に配置してURLを入力。
④スタックの詳細指定画面が開く。スタックの名前と必要なパラメータを入力して「次へ」をクリック。
⑤スタックオプションの設定画面が開く。デフォルトのままで「次へ」をクリック。
⑥設定内容確認のためのレビュー画面が開く。IAMロールを作成する場合はIAMリソース作成の承認チェックボックスにチェックを入れ「スタックの作成」をクリック。
以上でCloudFormationが実行されます。 正常に実行されるとステータスが"CREATE_COMPLETE"or"UPDATE_COMPLETE"と表示されます。
最後に
長くなってしまったためEC2構築後の設定やスクリプトについては次回記事で紹介します。
Share this post
Twitter
Facebook
Email