お久しぶりです。インフラソリューション部の“のなか”です。

今回は前回に続き、AWS上で稼働している既存WordPressを更新する際にかかっているデプロイ時間を30分から5分に圧縮した話をしていきます。

前編ではWordPressを構築しましたが、後編では自動デプロイを設定していきます。

それではWordPressのデプロイ自動化の後編を始めていきましょう!

注意点

前編で説明した内容は省略

前提条件

前編を実施済み

– SSHの公開鍵と秘密鍵を作成済み

– WSL2にzipパッケージをインストール済み

用語説明

EC2 Image Builderとは

後編に入る前にEC2 Image Builderについて説明します。

EC2 Image Builderとは公式ドキュメントのユーザーガイドで以下のように説明されています。

EC2 Image Builder is a fully managed AWS service that helps you to automate the creation, management, and deployment of customized, secure, and up-to-date server images. You can use the AWS Management Console, AWS Command Line Interface, or APIs to create custom images in your AWS account. 

説明を1行でまとめるとイメージの作成、管理、デプロイを自動化するツールです。例えば公式ドキュメントでは以下の図で説明されています。

以下の順番で実行し、この一連の処理をイメージパイプラインと言います。

1. イメージからインスタンスを起動

2. (ビルド)元のイメージにソフトウェアをインストール

3. インスタンスを終了

4. ソフトウェアをインストールイメージからインスタンスを起動

5. (テスト)作成したイメージをテスト

6. インスタンスを終了

7. テストしたイメージからゴールデンイメージを作成

イメージのビルドとテストが可能になるため、イメージの管理やセキュリティの担保がマネージドサービスから行えますが、2回インスタンスの起動と終了が行われるため、実行時間が30分程度かかります。

※実行時間はイメージ毎で異なります。

ドキュメントルート配下をデプロイするだけであれば、CodeDeployのみでも良いですが、EC2自体を更新したい場合は試してみると良いと思います。

後編のリソース作成手順

手順

1. TerraformでCodeCommitを作成

2. TerraformでDeployを作成

3. TerraformでCodePipelineを作成し、動作確認

4. TerraformでEC2 Image Builderを作成

5. TerraformでLambdaを作成

6. TerraformでCodePipelineにLambdaを追加し、動作確認

後編のシステム図と使用するAWSのリソース

システム図

使用するAWSのリソース

CodeCommit

CodeDeploy

CodePipeline

S3

Lambda

EC2ImageBuilder

後編のディレクトリ構成

Terraform

後編の~/terraform-blogは以下のディレクトリ構成になります。

.
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── hashicorp
│               └── aws
│                   └── 4.57.1
│                       └── linux_amd64
│                           └── terraform-provider-aws_v4.57.1_x5
├── .terraform.lock.hcl
├── README.md
├── aws_asg.tf
├── aws_codepipeline.tf
├── aws_data.tf
├── aws_ec2.tf
├── aws_iam.tf
├── aws_imagebuilder.tf
├── aws_s3.tf
├── aws_vpc.tf
├── lambda_function.py
├── lambda_function.py.zip
├── provider.tf
├── terraform.tfstate
└── terraform.tfstate.backup

構築

IAMを設定

1. AWSのコンソールにサインインし、Basic_Groupユーザーグループに以下ポリシーをアタッチ

– AmazonS3FullAccess

– AutoScalingFullAccess

– AWSCodeCommitFullAccess

– AWSCodeDeployFullAccess

– AWSCodePipeline_FullAccess

– AWSImageBuilderFullAccess

– AWSLambda_FullAccess

1. vi ~/terraform-blog/aws_iam.tfでROLE_BLOGロールのポリシーを追加

#----------------------------------------
# IAMロールを作成
#----------------------------------------
resource "aws_iam_role" "ROLE_BLOG" {
  name               = "ROLE_BLOG"
  assume_role_policy = "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}"
  description        = "Allows EC2 instances to call AWS services on your behalf."
  path               = "/"
  managed_policy_arns = [
    "arn:aws:iam::aws:policy/AmazonEC2FullAccess",
    "arn:aws:iam::aws:policy/AmazonSSMFullAccess",

    # 追加
    "arn:aws:iam::aws:policy/AmazonS3FullAccess",
    "arn:aws:iam::aws:policy/AWSCodePipeline_FullAccess",
    "arn:aws:iam::aws:policy/AWSImageBuilderFullAccess"
  ]
  tags = {
    Name = "ROLE_BLOG"
  }
}

CodeCommitを設定

まずはドキュメントルートを自動デプロイするため以下システムを構築していきます。

※①~④の説明は前編を確認して下さい。

1. BlogUserのセキュリティ認証情報画面で、AWS CodeCommitのSSH公開キーに作成済みのSSH公開鍵をアップロードして、SSHキーIDをメモ

1. WSL2でvi ~/terraform-blog/aws_codepipeline.tfを実行し、CodeCommitの設定を追記

resource "aws_codecommit_repository" "BlogCodecommitRepo" {
  repository_name = "BlogCodecommitRepo"
  tags = {
    Name = "BlogCodecommitRepo"
  }
}

1. Terraformを実行し、CodeCommitにリポジトリを作成

terraform planterraform apply

1. CMSサーバー(EC2)とCMSサーバー(ASG)にSSHし、AWS CodeCommitのSSH公開キーに登録した公開鍵をauthorized_keysに追記し、秘密鍵を配置

以下は秘密鍵がid_rsaの場合のディレクトリ例です。

[root@ip-10-0-0-10 .ssh]# ls -al合計 8drwx------ 2 root root   43  5月 16 10:26 .dr-xr-x--- 6 root root  185  5月 16 10:25 ..-rw------- 1 root root  553  5月 16 09:45 authorized_keys-rw------- 1 root root 1675  5月 16 10:25 id_rsa

1. vi ~/.ssh/configでSSHの設定ファイルを作成

以下は秘密鍵がid_rsaの場合の設定ファイルです。

Host git-codecommit.*.amazonaws.com
User (1でメモしたSSHキーIDをここに入力)
IdentityFile ~/.ssh/id_rsa

1. chmod 600 ~/.ssh/configでSSHの設定ファイルの権限を変更

drwx------ 2 root root   76  5月 12 10:35 .dr-xr-x--- 6 root root  203  5月 12 10:35 ..-rw------- 1 root root  553  5月  8 10:19 authorized_keys-rw------- 1 root root   89  5月  8 10:58 config-rw------- 1 root root 1675  5月  2 09:56 id_rsa-rw-r--r-- 1 root root 1230  5月  8 13:53 known_hosts

1. CMSサーバー(EC2)にSSHし、vi /var/www/html/appspec.ymlで自動デプロイするための設定ファイルを作成

version: 0.0os: linuxfiles:  - source: /    destination: /var/www/htmlfile_exists_behavior: OVERWRITE

1. CodeCommitのリポジトリにドキュメントルート配下をpush

cd /var/www/html/yum install -y gitgit init .git add .git commit -m "first push"git push --set-upstream ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/BlogCodecommitRepo master

※以降は以下のコマンドでCodeCommitにpushします。

cd /var/www/html/git add .git commit -m "change config"git push

CodeDeployを設定

1. WSL2でvi ~/terraform-blog/aws_codepipeline.tfを実行し、CodeDeployの設定を追記

#----------------------------------------
# CodeDeployのアプリケーションを作成
#----------------------------------------
resource "aws_codedeploy_app" "BlogCodedeployApp" {
  name             = "BlogCodedeployApp"
  compute_platform = "Server"
  tags = {
    Name = "BlogCodedeployApp"
  }
}

#----------------------------------------
# CodeDeployのデプロイグループを作成
#----------------------------------------
resource "aws_codedeploy_deployment_group" "BlogCodedeployDeployGroup" {
  app_name               = aws_codedeploy_app.BlogCodedeployApp.name
  deployment_group_name  = "BlogCodedeployDeployGroup"
  service_role_arn       = aws_iam_role.CodeDeployServiceRole.arn
  deployment_config_name = "CodeDeployDefault.AllAtOnce"
  autoscaling_groups = [aws_autoscaling_group.BlogASG.name]
  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }
  deployment_style {
    deployment_option = "WITHOUT_TRAFFIC_CONTROL"
    deployment_type   = "IN_PLACE"
  }
  tags = {
    Name = "BlogCodedeployDeployGroup"
  }
}

1. vi ~/terraform-blog/aws_iam.tfでCodeDeployのサービスロールの設定を追記

#----------------------------------------
# IAMロールを作成
#----------------------------------------
resource "aws_iam_role" "CodeDeployServiceRole" {
  name               = "CodeDeployServiceRole"
  assume_role_policy = "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"codedeploy.amazonaws.com\"},\"Sid\":\"\"}],\"Version\":\"2012-10-17\"}"
  description        = "Allows CodeDeploy to call AWS services such as Auto Scaling on your behalf."
  path               = "/"
  managed_policy_arns = [
    "arn:aws:iam::aws:policy/AWSCodeDeployFullAccess",
    "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
  ]
  tags = {
    Name = "CodeDeployServiceRole"
  }
}

1. Terraformを実行し、CodeDeployを作成

terraform planterraform apply

1. CMSサーバー(EC2)とCMSサーバー(ASG)にSSHし、CodeDeployのエージェントをインストール

sudo su -yum -y install ruby wget gitwget -P /tmp https://aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/installchmod +x /tmp/install/tmp/install auto

1. CMSサーバー(ASG)にSSHし、WordPressのドキュメントルートをクローン

cd /var/www/html/rm -rf /var/www/html/*git clone ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/BlogCodecommitRepo .

CodePipelineを設定

1. vi ~/terraform-blog/aws_s3.tfを実行し、データを格納するためのS3のバケットを作成

resource "aws_s3_bucket" "blogbucketwp" {
  bucket = "blogbucketwp"
  tags = {
    Name = "blogbucketwp"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "blogasbssec" {
  bucket = aws_s3_bucket.blogbucketwp.bucket
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
    bucket_key_enabled = true
  }
}

resource "aws_s3_bucket_request_payment_configuration" "blogbucketwpasbrpc" {
  bucket = aws_s3_bucket.blogbucketwp.bucket
  payer  = "BucketOwner"
}

1. WSL2でvi ~/terraform-blog/aws_codepipeline.tfを実行し、CodePipelineの設定を追記

#----------------------------------------
# CodeCommitのリポジトリを作成
#----------------------------------------
resource "aws_codecommit_repository" "BlogCodecommitRepo" {
  repository_name = "BlogCodecommitRepo"
  tags = {
    Name = "BlogCodecommitRepo"
  }
}

#----------------------------------------
# CodeDeployのアプリケーションを作成
#----------------------------------------
resource "aws_codedeploy_app" "BlogCodedeployApp" {
  name             = "BlogCodedeployApp"
  compute_platform = "Server"
  tags = {
    Name = "BlogCodedeployApp"
  }
}

#----------------------------------------
# CodeDeployのデプロイグループを作成
#----------------------------------------
resource "aws_codedeploy_deployment_group" "BlogCodedeployDeployGroup" {
  app_name               = aws_codedeploy_app.BlogCodedeployApp.name
  deployment_group_name  = "BlogCodedeployDeployGroup"
  service_role_arn       = aws_iam_role.CodeDeployServiceRole.arn
  deployment_config_name = "CodeDeployDefault.AllAtOnce"
  autoscaling_groups = [aws_autoscaling_group.BlogASG.name]
  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }
  deployment_style {
    deployment_option = "WITHOUT_TRAFFIC_CONTROL"
    deployment_type   = "IN_PLACE"
  }
  tags = {
    Name = "BlogCodedeployDeployGroup"
  }
}

#----------------------------------------
# CodePipelineを作成
#----------------------------------------
resource "aws_codepipeline" "BlogCodepipelinePipeline" {
  name = "BlogCodepipelinePipeline"
  role_arn = aws_iam_role.ServiceRoleCodePipeline.arn
  stage {
    name = "Source"
    action {
      name             = "Source"
      namespace        = "SourceVariables"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = "1"
      region           = "ap-northeast-1"
      output_artifacts = ["SourceArtifact"]
      configuration = {
        BranchName           = "master"
        OutputArtifactFormat = "CODE_ZIP"
        PollForSourceChanges = "true"
        RepositoryName       = aws_codecommit_repository.BlogCodecommitRepo.repository_name
      }
    }
  }
  stage {
    name = "Deploy"
    action {
      name            = "Deploy"
      namespace       = "DeployVariables"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CodeDeploy"
      version         = "1"
      region          = "ap-northeast-1"
      input_artifacts = ["SourceArtifact"]
      configuration = {
        ApplicationName     = aws_codedeploy_app.BlogCodedeployApp.name
        DeploymentGroupName = aws_codedeploy_deployment_group.BlogCodedeployDeployGroup.deployment_group_name
      }
    }
  }
  artifact_store {
    location = aws_s3_bucket.blogbucketwp.bucket
    type     = "S3"
  }
  tags = {
    Name = "BlogCodepipelinePipeline"
  }
}

1. vi ~/terraform-blog/aws_iam.tfでCodePipelineのサービスロールの設定を追記

#----------------------------------------
# IAMロールを作成
#----------------------------------------]
resource "aws_iam_role" "ServiceRoleCodePipeline" {
  name               = "ServiceRoleCodePipeline"
  assume_role_policy = "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"codepipeline.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}"
  description        = ""
  path               = "/service-role/"
  managed_policy_arns = [
    "arn:aws:iam::aws:policy/AWSCodePipeline_FullAccess",
    "arn:aws:iam::aws:policy/AWSCodeCommitFullAccess",
    "arn:aws:iam::aws:policy/AWSCodeDeployFullAccess",
    "arn:aws:iam::aws:policy/AWSLambda_FullAccess",
    "arn:aws:iam::aws:policy/AmazonS3FullAccess",
    "arn:aws:iam::aws:policy/AWSImageBuilderFullAccess",
    "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
  ]
  tags = {
    Name = "ServiceRoleCodePipeline"
  }
}

1. Terraformを実行し、CodePipelineを作成

terraform planterraform apply

1. CodePipelineのSourceとDeployが成功することを確認

自動デプロイの動作確認

1. CMSサーバー(EC2)にSSHし、権限を変更

sudo chmod 777 -R /var/www/html/wp-content/*

1. CMSサーバー(EC2)の管理画面のテーマエディターで背景色を修正

以下はデフォルトのテーマTwenty Twenty-Oneのstyle.cssのbodyの背景色を白色に修正した例です。

1. CMSサーバー(ASG)にブラウザからアクセスし、背景色がデフォルトであることを確認

1. CMSサーバー(EC2)にSSHし、ドキュメントルート配下をCodeCommitにpush

sudo su -cd /var/www/htmlgit add .git commit -m "change config"git push

1. CMSサーバー(ASG)にブラウザからアクセスし、背景色を修正されていることを確認

※CodePipelineが処理中であれば反映されていないことがあるため、5分程度おいて再度確認してください。

EC2 Image Builderを設定

1. WSL2でvi ~/terraform-blog/aws_data.tfを実行し、アカウント情報を取得する設定を追記

data "aws_caller_identity" "current" {}

1. vi ~/terraform-blog/aws_imagebuilder.tfを実行し、EC2 Image Builderの設定を追記

#----------------------------------------
# EC2 Image Builderのビルド用コンポーネントを作成
#----------------------------------------
resource "aws_imagebuilder_component" "BlogBuildComponent" {
  name                  = "BlogBuildComponent"
  platform              = "Linux"
  supported_os_versions = ["Amazon Linux 2"]
  version               = "1.0.0"
  data                  = "name: HelloWorldTestingDocument\ndescription: This is hello world testing document.\nschemaVersion: 1.0\n\nphases:\n  - name: build\n    steps:\n      - name: HelloWorldStep\n        action: ExecuteBash\n        inputs:\n          commands:\n            - echo \"Hello World! Build.\"\n\n  - name: validate\n    steps:\n      - name: HelloWorldStep\n        action: ExecuteBash\n        inputs:\n          commands:\n            - echo \"Hello World! Validate.\"\n\n  - name: test\n    steps:\n      - name: HelloWorldStep\n        action: ExecuteBash\n        inputs:\n          commands:\n            - echo \"Hello World! Test.\"\n"
  tags = {
    Name = "BlogBuildComponent"
  }
}

#----------------------------------------
# EC2 Image Builderのテスト用コンポーネントを作成
#----------------------------------------
resource "aws_imagebuilder_component" "BlogTestComponent" {
  name                  = "BlogTestComponent"
  platform              = "Linux"
  supported_os_versions = ["Amazon Linux 2"]
  version               = "1.0.0"
  data                  = "name: HelloWorldTestingDocument\ndescription: This is hello world testing document.\nschemaVersion: 1.0\n\nphases:\n  - name: test\n    steps:\n      - name: HelloWorldStep\n        action: ExecuteBash\n        inputs:\n          commands:\n            - echo \"Hello World! Test.\"\n"
  tags = {
    Name = "BlogTestComponent"
  }
}

#----------------------------------------
# EC2 Image Builderのイメージレシピを作成
#----------------------------------------
resource "aws_imagebuilder_image_recipe" "BlogImageRecipe" {
  name              = "BlogImageRecipe"
  parent_image      = aws_ami_from_instance.BlogAMI.id
  version           = "1.0.0"
  working_directory = "/tmp"
  block_device_mapping {
    device_name = "/dev/xvda"
    ebs {
      delete_on_termination = true
      encrypted = false
      volume_size = 8
      volume_type = "gp2"
    }
  }
  systems_manager_agent {
    uninstall_after_build = false
  }
  component {
    component_arn = aws_imagebuilder_component.BlogBuildComponent.arn
  }
  component {
    component_arn = aws_imagebuilder_component.BlogTestComponent.arn
  }
  tags = {
    Name = "BlogImageRecipe"
  }
}

#----------------------------------------
# EC2 Image Builderのイメージレシピを作成
#----------------------------------------
resource "aws_imagebuilder_infrastructure_configuration" "BlogInfra" {
  name                          = "BlogInfra"
  instance_profile_name         = aws_iam_role.ROLE_BLOG.name
  instance_types                = ["t2.micro"]
  key_pair                      = "KEY_BlogSample"
  security_group_ids            = [aws_security_group.sg_blog.id]
  subnet_id                     = aws_subnet.pub_subnet1a_blog.id
  terminate_instance_on_failure = true
  tags = {
    Name = "BlogInfra"
  }
}

#----------------------------------------
# EC2 Image Builderのディストリビューションを作成
#----------------------------------------
resource "aws_imagebuilder_distribution_configuration" "BlogDist" {
  name = "BlogDist"
  distribution {
    region = "ap-northeast-1"
    ami_distribution_configuration {
      name = "BlogAMI{{imagebuilder:buildDate}}"
      ami_tags = {
        Name = "BlogImageBuilderAMI"
      }
    }
    launch_template_configuration {
      account_id         = data.aws_caller_identity.current.id
      default            = true
      launch_template_id = aws_launch_template.BlogTemplate.id
    }
  }
  tags = {
    Name = "BlogDist"
  }
}

#----------------------------------------
# EC2 Image Builderのイメージパイプラインを作成
#----------------------------------------
resource "aws_imagebuilder_image_pipeline" "BlogImagePipeline" {
  name                             = "BlogImagePipeline"
  status                           = "ENABLED"
  distribution_configuration_arn   = aws_imagebuilder_distribution_configuration.BlogDist.arn
  enhanced_image_metadata_enabled  = true
  image_recipe_arn                 = aws_imagebuilder_image_recipe.BlogImageRecipe.arn
  infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.BlogInfra.arn
  image_tests_configuration {
    image_tests_enabled = true
    timeout_minutes     = 720
  }
  tags = {
    Name = "BlogImagePipeline"
  }
}

1. Terraformを実行し、イメージパイプラインを作成

terraform planterraform apply

1. イメージパイプラインを実行し、動作確認

以下のようにエラーが出力されていないことを確認します。

CodePipelineでEC2 Image Builderを呼び出す

1. WSL2でvi ~/terraform-blog/lambda_function.pyを実行し、EC2 Image Builderを呼び出すLambdaのソースコードを作成

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')

# AMI作成の成功時の処理
def put_job_success(job_id):
    logger.info('Putting job success')
    codepipeline_client.put_job_success_result(jobId=job_id)

# AMI作成中の時の処理
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
    )

# AMI作成の失敗時の処理
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
        }
    )

# codepipelineからlambda_functionを呼び出し
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']
      pipeline_name = os.environ['CODEPIPELINE_NAME']

      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('ImageBuilderVersionArn is %s', image_build_version_arn)

          # ビルドの状態を取得
          response = imagebuilder_client.get_image(
            imageBuildVersionArn = image_build_version_arn
          )
          build_status = response['image']['state']['status']
          logger.info('ImageBuild Status is %s',build_status)

          # 処理の結果が成功なら状態をAVAILABLEに遷移
          if build_status == 'AVAILABLE':
              put_job_success(job_id)
          # 処理の結果が失敗なら状態をFAILEDに遷移
          elif build_status == 'FAILED':
              errmsg='Build Error'
              put_job_failure(job_id, errmsg)
          # 処理が継続中の場合continue_job_later関数を呼び出す
          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()
        put_job_failure(job_id, 'Function exception: ' + str(err))

    logger.info('Function complete')
    return "Complete."

1. Lambdaのソースコードをzip形式で圧縮

cd ~/terraform-blog/sudo zip lambda_function.py.zip lambda_function.py

1. vi ~/terraform-blog/aws_s3.tfを実行し、Lambdaのソースコードを保持するS3の設定を追記

resource "aws_s3_object" "lambdacode" {
  bucket = aws_s3_bucket.blogbucketwp.bucket
  key = "lambda_function.py.zip"
  source = "~/terraform-blog/lambda_function.py.zip"
  etag = filemd5("~/terraform-blog/lambda_function.py.zip")
}

1. Terraformを実行し、Lambdaのソースコードを保持するS3を作成

terraform planterraform apply

1. vi ~/terraform-blog/aws_codepipeline.tfを実行し、Lambdaリソースを作成する設定とLambdaを呼び出す設定を追記するためファイルを書き換える

#----------------------------------------
# CodeCommitのリポジトリを作成
#----------------------------------------
resource "aws_codecommit_repository" "BlogCodecommitRepo" {
  repository_name = "BlogCodecommitRepo"
  tags = {
    Name = "BlogCodecommitRepo"
  }
}

#----------------------------------------
# CodeDeployのアプリケーションを作成
#----------------------------------------
resource "aws_codedeploy_app" "BlogCodedeployApp" {
  name             = "BlogCodedeployApp"
  compute_platform = "Server"
  tags = {
    Name = "BlogCodedeployApp"
  }
}

#----------------------------------------
# CodeDeployのデプロイグループを作成
#----------------------------------------
resource "aws_codedeploy_deployment_group" "BlogCodedeployDeployGroup" {
  app_name               = aws_codedeploy_app.BlogCodedeployApp.name
  deployment_group_name  = "BlogCodedeployDeployGroup"
  service_role_arn       = aws_iam_role.CodeDeployServiceRole.arn
  deployment_config_name = "CodeDeployDefault.AllAtOnce"
  autoscaling_groups = [aws_autoscaling_group.BlogASG.name]
  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }
  deployment_style {
    deployment_option = "WITHOUT_TRAFFIC_CONTROL"
    deployment_type   = "IN_PLACE"
  }
  tags = {
    Name = "BlogCodedeployDeployGroup"
  }
}

#----------------------------------------
# Lambdaを作成
#----------------------------------------
resource "aws_lambda_function" "BlogImageBuilderFunction" {
  architectures                  = ["x86_64"]
  s3_bucket                      = aws_s3_bucket.blogbucketwp.bucket
  s3_key                         = "lambda_function.py.zip"
  function_name                  = "BlogImageBuilderFunction"
  handler                        = "lambda_function.lambda_handler"
  memory_size                    = 128
  package_type                   = "Zip"
  reserved_concurrent_executions = -1
  role                           = aws_iam_role.ServiceRoleLambda.arn
  runtime                        = "python3.9"
  timeout                        = 3
  tracing_config {
    mode = "PassThrough"
  }
  ephemeral_storage {
    size = 512
  }
  environment {
    variables = {
      CODEPIPELINE_NAME  = "BlogCodepipelinePipeline"
      IMAGE_PIPELINE_ARN = aws_imagebuilder_image_pipeline.BlogImagePipeline.arn
    }
  }
  tags = {
    Name = "BlogImageBuilderFunction"
  }
}

#----------------------------------------
# CodePipelineを作成
#----------------------------------------
resource "aws_codepipeline" "BlogCodepipelinePipeline" {
  name = "BlogCodepipelinePipeline"
  role_arn = aws_iam_role.ServiceRoleCodePipeline.arn
  stage {
    name = "Source"
    action {
      name             = "Source"
      namespace        = "SourceVariables"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = "1"
      region           = "ap-northeast-1"
      output_artifacts = ["SourceArtifact"]
      configuration = {
        BranchName           = "master"
        OutputArtifactFormat = "CODE_ZIP"
        PollForSourceChanges = "true"
        RepositoryName       = aws_codecommit_repository.BlogCodecommitRepo.repository_name
      }
    }
  }
  stage {
    name = "Deploy"
    action {
      name            = "Deploy"
      namespace       = "DeployVariables"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CodeDeploy"
      version         = "1"
      region          = "ap-northeast-1"
      input_artifacts = ["SourceArtifact"]
      configuration = {
        ApplicationName     = aws_codedeploy_app.BlogCodedeployApp.name
        DeploymentGroupName = aws_codedeploy_deployment_group.BlogCodedeployDeployGroup.deployment_group_name
      }
    }
  }
  stage {
    name = "Invoke"
    action {
      name             = aws_lambda_function.BlogImageBuilderFunction.function_name
      namespace        = "InvokeVariables"
      category         = "Invoke"
      owner            = "AWS"
      provider         = "Lambda"
      version          = "1"
      region           = "ap-northeast-1"
      input_artifacts  = []
      output_artifacts = []
      configuration = {
        FunctionName = aws_lambda_function.BlogImageBuilderFunction.function_name
      }
    }
  }
  artifact_store {
    location = aws_s3_bucket.blogbucketwp.bucket
    type     = "S3"
  }
  tags = {
    Name = "BlogCodepipelinePipeline"
  }
}

1. Terraformを実行し、CodePipelineを作成

terraform planterraform apply

1. 作成したCodePipelineを実行し、動作確認

動作確認

1. CMSサーバー(EC2)の管理画面のテーマエディターで背景色を修正

以下はデフォルトのテーマTwenty Twenty-Oneのstyle.cssのbodyの背景色を黒色に修正した例です。

1. CMSサーバー(ASG)にブラウザからアクセスし、背景色が白色であることを確認

1. CMSサーバー(EC2)にSSHし、ドキュメントルート配下をCodeCommitにpush

sudo su -cd /var/www/htmlgit add .git commit -m "change config"git push

1. CMSサーバー(ASG)にブラウザからアクセスし、背景色を修正されていることを確認

※CodePipelineが処理中であれば反映されていないことがあるため、30分程度おいて再度確認してください。

改善点

今回は権限を非常に緩く設定しましたが、実際に使用する際は適宜読み替えて権限を設定してください。

バージョン管理もgitを使ったことがない人でも使えるようにコマンドを固定していますが、コンフリクトが発生する等のリスクがあるため、gitを使える人はブランチを切ったりコミットメッセージを変更する等適宜読み替えてください。

また、イメージの管理にEC2 Image Builderを使用しましたが、ゴールデンイメージ作成に30分程度かかるため、ビルドとテストを行う必要が無ければAWS Backupを使用する等の別の方法を使用した方が良いです。

IAMをTerraformでresourceで管理していますが、data sourceを使用した方がスクラップ&ビルドしやすいです。

所感

今回はイメージの管理を自動化しましたが、EC2からAMIを取得する作業や、AMIが正常に取得しているかの確認をする作業がマネージドサービスで自動化できるため非常に便利でした。

前編と合わせて、ドキュメントルート配下の自動デプロイ、AMI管理の自動化、Ansibleによるプロビジョニングの自動化、Terraformによるインフラのコード化等の様々なツールを紹介しましたが、全てのツールを使用しなくても何か一つでも試すと作業が快適になると思います。

前編と後編を合わせるとかなりボリュームがありますが、ここまで読んで頂きありがとうございました。

SHARE

  • facebook
  • twitter

SQRIPTER

AGEST Engineers

AGEST

記事一覧

AGESTのエンジニアが情報発信してます!
AGESTのサービスやソリューションのお問い合わせページはこちらです。

株式会社AGEST

Sqriptsはシステム開発における品質(Quality)を中心に、エンジニアが”理解しやすい”Scriptに変換して情報発信するメディアです

  • 新規登録/ログイン
  • 株式会社AGEST