この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
EC2 Image Builderで作成されるイメージはゴールデンイメージとなるため、カスタム済みの設定ファイルなどをイメージの中に含めるかと思います。設定ファイルをCodeCommitで管理しているケースを想定し、PushにてEC2 Image Builderを起動し、ゴールデンイメージの作成を自動化する構成を考えてみました。
構成
CodeCommitにPushした設定ファイルをパイプライン処理でS3にデプロイします。その後、Lambda Function呼び出し、EC2 Image Builderを起動させ、S3にデプロイされた設定ファイルを含む形でイメージを作成。イメージ作成完了でSNSへPublishするような構成です。
EC2 Image Builder
以下のビルドコンポーネントを有するイメージパイプラインを作成しました。ここではApacheをインストールして、設定ファイル(httpd.conf
)をS3より取得する定義になります。この設定ファイルをCodeCommitの管理対象としています。
name: Apache Config
description: This is Apache Config testing document.
schemaVersion: 1.0
phases:
- name: build
steps:
- name: InstallLinuxUpdate
action: UpdateOS
- name: InstallApache
action: ExecuteBash
inputs:
commands:
- sudo yum install httpd -y
- sudo systemctl enable httpd
- sudo systemctl start httpd
- name: DownloadConfig
action: S3Download
inputs:
- source: s3://test-pipeline-deploy/etc/httpd/conf/httpd.conf
destination: /etc/httpd/conf/httpd.conf
- name: StartApache
action: ExecuteBash
inputs:
commands:
- sudo systemctl enable httpd
Lambda Function
イメージパイプラインを実行するLambda Functionです。このLambda FunctionはCodePipelineより呼び出しされます。
import boto3
import logging
import json
import os
import traceback
logger = logging.getLogger()
logger.setLevel(logging.INFO)
codepipeline_client = boto3.client('codepipeline')
imagebuilder_client = boto3.client('imagebuilder')
sns_client = boto3.client('sns')
#正常終了
def put_job_success(job_id):
logger.info('Putting job success')
codepipeline_client.put_job_success_result(jobId=job_id)
#処理継続/CodePipelineに継続トークン返却
def continue_job_later(job_id,image_build_version_arn):
logger.info('Putting job continuation')
continuation_token = json.dumps({'ImageBuildVersionArn':image_build_version_arn})
codepipeline_client.put_job_success_result(
jobId=job_id,
continuationToken=continuation_token
)
#異常終了
def put_job_failure(job_id, err):
logger.error('Putting job failed')
message = str(err)
codepipeline_client.put_job_failure_result(
jobId=job_id,
failureDetails={
'type': 'JobFailed',
'message': message
}
)
#SNS通知
def sns_publish(sns_topic_arn, pipeline_name, job_id, job_status):
logger.info('Publish to SNS topic')
message = 'PipelineName: ' + pipeline_name + '\n'
message += 'JobId: ' + job_id + '\n'
message += 'Status: ' + job_status + '\n'
res = sns_client.publish(
TopicArn=sns_topic_arn,
Message=message
)
messaeg_id = res['MessageId']
logger.info('SNS Messaeg ID is %s', messaeg_id)
def lambda_handler(event, context):
try:
job_id = event['CodePipeline.job']['id']
job_data = event['CodePipeline.job']['data']
image_pipeline_arn = os.environ['IMAGE_PIPELINE_ARN']
#CodePipelineユーザーパラメーター取得
user_parameters = json.loads(
job_data['actionConfiguration']['configuration']['UserParameters']
)
pipeline_name = user_parameters['PipelineName']
sns_topic_arn = user_parameters['SnsTopicArn']
logger.info('ImagePipelineArn is %s', image_pipeline_arn)
logger.info('CodePipeline Event is %s',event['CodePipeline.job'])
#継続トークン有無確認
if 'continuationToken' in job_data:
continuation_token = json.loads(job_data['continuationToken'])
image_build_version_arn = continuation_token['ImageBuildVersionArn']
logger.info(image_build_version_arn)
#ビルドの状態取得
response = imagebuilder_client.get_image(
imageBuildVersionArn = image_build_version_arn
)
build_status = response['image']['state']['status']
logger.info(build_status)
if build_status == 'AVAILABLE':
sns_publish(sns_topic_arn, pipeline_name, job_id, job_status='success')
put_job_success(job_id)
elif build_status == 'FAILED':
sns_publish(sns_topic_arn, pipeline_name, job_id, job_status='failed')
errmsg='Build Error'
put_job_failure(job_id, errmsg)
else:
continue_job_later(job_id,image_build_version_arn)
else:
#ビルド実行
response = imagebuilder_client.start_image_pipeline_execution(
imagePipelineArn=image_pipeline_arn
)
image_build_version_arn = response['imageBuildVersionArn']
logger.info('imageBuildVersionArn is %s', image_build_version_arn)
continue_job_later(job_id,image_build_version_arn)
except Exception as err:
logger.error('Function exception: %s', err)
traceback.print_exc()
sns_publish(sns_topic_arn, pipeline_name, job_id, job_status='failed')
put_job_failure(job_id, 'Function exception: ' + str(err))
logger.info('Function complete')
return "Complete."
イメージパイプラインは非同期で実行されるため、イメージの取得完了を把握すべく継続トークンを利用しました。Lambda Functionの実行結果に継続トークンが含まれる限り、CodePipelineによってLambda Functionが再実行される仕組みです。
また、CodePipelineにLambda Functionの終了を返すには、PutJobSuccessResultなどが必要になります。(今回はPython3.8を利用しているので、put_job_success_result) これらを定義しないと、Lambda Functionの処理が終わっても、パイプラインステージは終了しませんのでご注意ください。
CodePipeline
ステージを3つ作成しました。
Sourceステージ
アクションプロバイダーはCodeCommitを指定し、設定ファイルを管理するリポジトリを指定しています。
Deployステージ
アクションプロバイダーはS3を指定し、CodeCommitにPushした設定ファイルをデプロイするS3バケットを指定しています。
Buildステージ
アクションプロバイダーはAWS Lambdaを指定し、先に作成したLambda Functionを指定しています。
Lambda Functionの終了でSNSへPublishできるよう、ユーザーパラメーターにパイプライン名、SNSトピックARNの情報をJSONで設定しています。このパラメーターはCodePipelineからLambda Functionが呼び出される際にイベントとして渡され、Lambda Function内で参照することができます。以下のJSONは見やすいように改行していますが、実際の設定は改行を含めずに設定しています。
{
"PipelineName": "TestCodePipeline",
"SnsTopicArn": "arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:test-topic"
}
動かしてみた
設定ファイルを更新(ここでは、Apacheのバージョンを非表示にする設定)して、CodeCommitにPushしました。Pushをトリガーにパイプライン処理が実行されます。
CodeCommitには、設定ファイルが格納されます。
Deployステージが終了すると、指定したS3バケットに設定ファイルがデプロイされています。
Buildステージでは、Lambda Functionが起動され、イメージパイプラインが実行されます。
Lambda Functionは継続トークンを利用しているため、イメージパイプラインが終了するまで繰り返しCodePipelineから呼び出しされます。
イメージパイプラインが終了すると、Buildステージも終了します。
- イメージパイプライン
-
Buildステージ
取得したイメージよりEC2を起動し、Pushした設定ファイルが含まれているか(設定が反映されているか)確認してみます。
Apacheへアクセスし、ヘッダ情報よりバージョンが非表示になっていることが確認できました。(トップページを配置していないので403が返っています)
$ curl -I http://54.238.185.199
HTTP/1.1 403 Forbidden
Date: Thu, 23 Jan 2020 10:08:56 GMT
Server: Apache
Upgrade: h2,h2c
Connection: Upgrade
Last-Modified: Tue, 22 Oct 2019 22:56:48 GMT
ETag: "e2e-59587b710ac00"
Accept-Ranges: bytes
Content-Length: 3630
Content-Type: text/html; charset=UTF-8
続いてはバージョンを表示する変更を加え、CodeCommitに設定ファイルをPushしました。Push後の動作は先程と同様で、パイプライン処理が実行されます。
新たに取得したイメージよりEC2を起動し、設定が反映されているか確認してみます。
ヘッダ情報にバージョンが表示され、設定が反映されていることが確認できました。
$ curl -I http://54.249.34.149
HTTP/1.1 403 Forbidden
Date: Thu, 23 Jan 2020 10:10:39 GMT
Server: Apache/2.4.41 ()
Upgrade: h2,h2c
Connection: Upgrade
Last-Modified: Tue, 22 Oct 2019 22:56:48 GMT
ETag: "e2e-59587b710ac00"
Accept-Ranges: bytes
Content-Length: 3630
Content-Type: text/html; charset=UTF-8
さいごに
Lambda Function部分をCodeBuildに置き換えたり、Step Functionsを利用するなど同様のことは他の構成でもできそうな気はします。 比較的?手数が少なそうな構成で、リポジトリへのファイルPushでゴールデンイメージを自動作成する方法をご紹介しました。CodePipelineからAWS Lambdaの呼び出しアクションのタイムアウトは1時間となっていますので、イメージの作成に1時間以上かかる場合は、別の構成を検討する必要があります。ちなみに、今回の構成では実行から終了までざっくり20分〜30分程度でした。